iOS:“锁”与“死锁”,谈谈同步与互斥
2023-04-19 09:33:20
在多线程世界中驾驭锁的艺术
并发编程的基石:锁
在多线程编程的浩瀚世界中,同步和互斥是永恒的话题。而锁,正是在此舞台上闪耀的明星,担当着维持秩序、协调进程的重任。在 iOS 开发中,pthreads、NSLock、dispatch_semaphore 等锁机制可谓是如雷贯耳,它们各有千秋,需要根据不同场景妥善选择。
线程同步的三大隐患
-
死锁: 想象两个线程 A 和 B,A 紧抓资源 A 不放,等待 B 释放资源 B;而 B 也紧紧抱住资源 B,等待 A 释放资源 A。这种互相等待,互相制约的局面,就形成了死锁,让两个线程都陷入僵局。
-
饥饿: 有时候,一个线程会被晾在一边,眼巴巴看着其他线程轮流享用资源,永远轮不到自己上场。这种可怜兮兮的状态,就是饥饿。
-
不公平: 在竞争资源的线程队伍中,有的线程天生就有特权,总能优先获得资源,而有的线程只能望洋兴叹。这就是不公平。
锁的种类:互斥与读写,用户态与内核态
根据允许的并发访问程度,锁可分为互斥锁 和读写锁 。互斥锁不容许多个线程同时访问资源,严格遵循“你用我等,我用你等”的原则。读写锁则比较宽容,允许多个线程同时读取资源,但只允许一个线程写入资源,好比图书馆中的阅览室,大家可以一起读书,但只能一人动笔写字。
从运行环境的角度看,锁还可以划分为用户态锁 和内核态锁 。用户态锁在应用程序的领地内运行,不需要系统调用,速度快如闪电。内核态锁则是在操作系统的庇护下工作,需要系统调用的协助,性能相对较慢。
选择合适的锁:因材施教
在锁的选择上,没有一刀切的方案。需要综合考虑以下因素:
-
资源性质: 有的资源天生独占,只能由一个线程染指;有的资源则能广纳百川,允许多个线程同时享用。
-
性能需求: 对于时间敏感型任务,选择高性能的锁至关重要。
-
公平性要求: 如果公平是你的追求,那么公平锁可以满足你的需求。
-
死锁容忍度: 对于死锁敏感的场景,需要选择不易产生死锁的锁机制。
锁的注意事项:谨防陷阱
在使用锁的汪洋大海中,稍有不慎,就会落入暗礁之中。以下几点注意事项,请牢记于心:
-
临界区轻装上阵: 被锁守护的代码区域,也就是临界区,应该尽可能精简,避免长时间霸占资源,让其他线程望眼欲穿。
-
锁中嵌套莫尝试: 千万不要在锁中再套一层锁,这是死锁的温床。
-
系统调用要谨慎: 在锁的怀抱中进行系统调用,犹如在虎口拔牙,稍有闪失,就会让线程陷入阻塞的泥潭。
-
锁中解锁禁行令: 在锁的内部释放锁,会让程序陷入死胡同,得不偿失。
结论:以锁为媒,掌控并发
锁,是多线程世界中不可或缺的利器。合理选择,恰当使用,可以化繁为简,化危为安,让多线程程序井然有序,性能卓绝。而对锁的深入理解,正是驾驭并发编程的关键所在。
常见问题解答
-
pthreads 和 NSLock 有什么区别?
pthreads 是跨平台的锁机制,性能优秀,但需要手动管理。NSLock 是苹果官方提供的 Objective-C 封装,使用更加方便,但性能略逊于 pthreads。 -
什么时候应该使用读写锁?
当多个线程需要同时读取共享资源,但只有少数线程需要修改资源时,读写锁非常适合。 -
死锁是如何发生的?
死锁通常发生在多个线程相互等待资源释放时,形成一个循环等待的链条。 -
饥饿是如何避免的?
使用公平锁机制或采用优先级调度算法,可以减少饥饿发生的概率。 -
锁和信号量有什么关系?
信号量是一种特殊的锁机制,它可以用来控制资源的访问数量,确保资源的使用不会超出限制。