iOS 底层探索篇 ——八大锁的分析

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

1. 锁的类型

自旋锁:线程反复检查锁变量是否可用。由于线程在这一过程中保持执行, 因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

互斥锁:是一种用于多线程编程中,防止两条线程同时对同一公共资源(比 如全局变量)进行读写的机制有互斥和同步两个特点。当一个线程在进行任务的时候,别的线程就不能进行这个任务,这就是互斥。别的线程按照一定的顺序在这个线程完成后执行,这就是同步。该目的通过将代码切片成一个一个的临界区而达成,互斥锁分为递归锁和非递归锁。这里属于互斥锁的有:

  • NSLock
  • pthread_mutex
  • @synchronized

条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行

  • NSCondition
  • NSConditionLock

递归锁:就是同一个线程可以加锁N次而不会引发死锁

  • NSRecursiveLock
  • pthread_mutex(recursive)

信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是 semaphore 在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。

  • dispatch_semaphore

其实基本的锁就包括了三类 自旋锁 互斥锁 读写锁
其他的比如条件锁,递归锁,信号量都是上层的封装和实现!

2. 锁的应用

这里的代码运行后会奔溃,崩溃的主要原因是testArray在某一瞬间变成了nil。

在这里插入图片描述

在这里插入图片描述

下面这段代码如果执行的话,就会有10个testMethod(10)在异步执行,那么如何确保他们一个接一个执行呢?也就是应该怎么加锁呢?

在这里插入图片描述

在这里插入图片描述

这里把锁加在testMethod(10)前后就解决了问题,这里也可以在testMethod声明之后加锁。这里不能在testMethod的代码块里面加锁,否则就会递归加锁,而NSLock不支持递归加锁。

在这里插入图片描述

那么如果加了递归锁,是否可以这样加锁呢?答案也是不行的,因为这里面是多线程环境,递归锁无法进行多线程可递归。而要多线程,可递归,则需要用@synchronized锁。

在这里插入图片描述

这里看到加了@synchronized锁之后是正常运行的。

3. 锁的底层实现

看到NSLock,这里面是遵守了NSLocking的协议,在foundation框架里面。

在这里插入图片描述

接下来去Foundation里面寻找NSLock,看到其底层实现是关于pthread_mutex的封装,其加锁解锁也是对
pthread_mutex相关API的调用。

在这里插入图片描述

在这里插入图片描述

在看到递归锁NSRecursiveLock,发现这里初始化的时候多了一个pthread_mutexattr_settype的调用,并且将类型设置为Recursive类型。

在这里插入图片描述

NSCondition里面则是添加了condition的处理。

在这里插入图片描述

4. NSConditionLock的探索

这里结合NSConditionLock来探索。
下面的执行结果是什么呢?这里执行的结果是3乱序,但是2会在1的前面。这里用了NSConditionLock,当condition等于2的时候,才会执行,所以2一定会比1先执行。线程3里面因为没有加条件,所以只是普通的加锁解锁,直接执行。

在这里插入图片描述

那么这里就出现了几个问题。

  • NSConditionLock 和 NSCondition 有什么区别呢
  • 初始化传的参数2是什么呢
  • lockWhenCondition是如何控制的呢
  • unlockWithCondition做了什么呢

4.1 [NSConditionLock initWithCondition :]

打下断点后运行。运行后添加符号断点NSConditionLock initWithCondition:

在这里插入图片描述

接下来就进入到initWithCondition方法的汇编了。

在这里插入图片描述

接着读取一下寄存器,发现都匹配上了。

在这里插入图片描述

接下来在汇编里面把bl全都打上断点,因为bl是跳转的地方。打完断点后继续运行来到第一个BL。

这里读取寄存器,发现是一个不知道什么的对象调用一个init方法,参数为2.

在这里插入图片描述

继续往下走,发现这里是一个NSConditionLock对象调用Init方法.参数为2。

在这里插入图片描述

继续往下走,发现这里是一个NSConditionLock对象调用zone方法.参数为2。

在这里插入图片描述

继续往下走,发现这里是一个NSCondition调用allocWithZone方法,参数没有了

在这里插入图片描述

继续往下走,发现这里是一个NSCondition对象调用init方法.

在这里插入图片描述

继续往下走,看到return,也就是[NSConditionLock initWithCondition :]这个方法的返回值。发现返回了NSConditionLock。

在这里插入图片描述

x/8gx 查看NSConditionLock对象的成员变量,发现NSCondition对象成了NSConditionLock对象的一个成员变量。这里的NSCondition对象是之前创建的那一个。之前的参数2也是NSConditionLock对象的一个成员变量。

在这里插入图片描述

到这里可以的得到,NSConditionLock里面封装了一个NSCondition,并且将之前传进来的2保存了下来。

  1. ?对象 调用init。
  2. [NSConditionLock init:2]
  3. [NSConditionLock zone:2]
  4. [NSCondition allocWithZone]
  5. [NSCondition init]

4.2 [NSConditionLock lockWhenCondition:]

接下来看lockWhenCondition。下一个[NSConditionLock lockWhenCondition:]的断点,运行来到汇编后,在bl打下断点。

在这里插入图片描述

读取一下寄存器,发现是一个NSDate对象调用了distantFuture方法。

在这里插入图片描述

继续往下走之后读取寄存器,发现是一个NSConditionLock对象调用了lockWhenCondition:beforeDate:方法。这里多读取一个x3因为是两个参数,x3感觉像是distantFuture方法的返回值。

在这里插入图片描述

继续下[NSConditionLock lockWhenCondition:beforeDate:]的符号断点,进入后向跳转的地方打下断点。这里8的断点没有东西,断到之后往下跳到13。

在这里插入图片描述

读取寄存器后发现,这里的NSCondition对象调用了一个lock。

在这里插入图片描述

往下跳发现没什么东西,继续往下走。

在这里插入图片描述

这里读取寄存器发现NSCondition对象调用了一个waitUntilDate的方法。参数为[NSConditionLock lockWhenCondition:beforeDate:]传进来的参数。

在这里插入图片描述

接着会跳到另外一个线程的[NSConditionLock lockWhenCondition:beforeDate:],因为这里就锁住了不走了。跳过来之后走到刚才那一步的下一步,发现没有什么东西。

在这里插入图片描述

继续往下走,发现调用了一个NSCondition对象的unlock方法。

在这里插入图片描述

继续往下走到return,发现返回了1。

在这里插入图片描述

[NSConditionLock lockWhenCondition:]

  1. [NSDate distantFuture:];
  2. [NSConditionLock lockWhenCondition:beforeDate:]
    2.1 [NSCondition lock];
    2.2 [ NSCondition waitUntilDate:]
    2.3 [NSCondition unlock];
    2.4 return 1

4.3 [NSConditionLock unlockWithCondition :]

接着往下走,就会来到unlockWithCondition方法里面,进来后下好断点,接着往下走。

在这里插入图片描述

读取寄存器,发现这里NSCondition对象调用了lock方法。

在这里插入图片描述

继续往下走读取寄存器,发现这里NSCondition对象调用了broadcast方法。

在这里插入图片描述

继续往下走,发现调用了一个NSCondition对象的unlock方法。

在这里插入图片描述

[NSConditionLock unlockWithCondition :]

  1. [NSCondition lock];
  2. [NSCondition broadcast];
  3. [NSCondition unlock];

这里走完unlockWithCondition之后,又回到了之前的线程,进行了unlock的操作。

在这里插入图片描述

然后return的地方返回值也为1.

在这里插入图片描述

然后接下来就走到了下面这里。

在这里插入图片描述

也就是说这里的执行顺序为:

  1. [conditionLock lockWhenCondition:1];
  2. [conditionLock lockWhenCondition:2];
  3. [conditionLock unlockWithCondition:1];
  4. [conditionLock unlockWithCondition:0];

5. 底层源码

这里去看一下底层源码是否是这样实现的。
首先看到init,这里确实有两个属性,一个_cond = NSCondition() 和一个_value: Int。并且在初始化的时候对_value进行赋值,赋值为传进来的参数condition。并且_cond进行了初始化。

在这里插入图片描述

接下来看[NSConditionLock lockWhenCondition:]。发现这里确实调用了Date.distantFuture 和 [NSConditionLock lockWhenCondition:beforeDate:]

在这里插入图片描述

在这里插入图片描述

接下来来到[NSConditionLock lockWhenCondition:beforeDate:],发现有_cond的lock和Unlock方法,也就是[NSCondition lock]; 和 [NSCondition unlock];并且当_thread != nil || _value != condition 的时候,就会调用!_cond.wait(until: limit)。超时的话就解锁,返回false。否则就正常解锁,返回true。

在这里插入图片描述

再来看[NSConditionLock unlockWithCondition :],看到这里做了_cond的lock和Unlock方法,并且中间调用了broadcast。

在这里插入图片描述

6. 读写锁

除了互斥锁和自旋锁,还有一个锁就是读写锁

读写锁实际是一种特殊的互斥锁,它把对共享资源的访问者划分成读者写者读者只对共享资源 进行读访问写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU 数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里, 直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
实现读写锁需要做到:

  • 多读单写
  • 写和写互斥
  • 读写互斥
  • 写不能堵塞任务执行

用GCD实现读写锁的关键是栅栏函数
这里setter使用barrier_async不会堵塞其他线程的执行,并且会等待前面的操作完成后在执行,并且能确保单写。
这里读的时候用的时候sync,这样确保取值之后在返回,并且因为是多线程,所以可以实现多读。而这里的sync又能确保读写互斥

在这里插入图片描述

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享