iOS底层分析-锁(四)

900 阅读5分钟

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

锁的分类

我们通常将锁分为自旋锁互斥锁两大类;

  • 互斥锁:
    • 互斥锁,有互斥同步两个特性;我们的程序在运行过程中,会有多个线程在进行相关的处理,可能会同时处理任务,那么互斥的意思是,当A线程在进行任务操作时,B线程就不能进行操作(闲着);并且需要按照顺序执行;
    • 互斥锁还分为递归锁非递归锁
  • 自旋锁:
    • 同样的两个线程在执行相关的任务,当A线程在处理任务的时候,B线程一直在等待;相当于在互斥的基础上再加上忙等;(自旋锁也相当于一种特殊的互斥锁)
  • 第三种(读写锁)

NSLock和NSRelock分析

NSLock

我们来分析一段代码:

很明显,这段代码存在线程不安全的的情况,运行如下:

想要解决崩溃问题,我们可以使用NSLock进行处理:

我们接下来,再看一段代码演示:

我们可以看到,输入结果没有问题,如果我们在外边加一层for循环呢?

我们看到数据的打印结果已经发生混乱,因为多线程引起的线程不安全导致的;

接下来,我们使用NSLock给计算流程加锁:

我们只要在testMethod函数之前前lock加锁,执行后unlock进行解锁就可以解决问题了;

此处在使用NSLock的过程中,要防止递归加锁的情况出现;因为NSLock是一把非递归锁;

比如这样:

将会导致递归加锁的情况;

那么,既然NSLock是一把非递归锁,是用的时候要避免递归加锁的情况出现,那么我们直接使用递归锁是不是就能避免这种情况呢?我们用@synchronized这把递归锁尝试一下:

NSRecursiveLock

那么,我们能否使用NSRecursiveLock来解决上述问题呢?

我们修改一下代码,如下:

结果,打印了一遍之后,程序崩溃;这是因为我们的程序是多线程执行的,而NSRecursiveLock并不是一把多线程可递归锁;我们在使用的时候,要注意多线程的问题:

NSRecursiveLock可以解决锁的可递归性,但是却无法解决多线程问题,所以这种情况,我们推荐使用具有多线程可递归性@synchronized来解决问题:

NSCondition分析

NSCondition的对象实际上作为一个锁和一个线程检查器:主要为了当检测条件时保护数据源,执行条件触发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞;

相关API的使用:

  • [condition lock]:一般用于多线程同时访问和修改同一个数据源,保证在同一个时间内数据源只被访问和修改一次,其他线程的命令需要在lock外等待,直到unlock才可继续访问;
  • [condition unlock]:与lock配对使用;
  • [condition wait]:让当前线程处于等待状态;
  • [condition signal]CPU发信号告诉线程不用再等待,可以继续执行;

我们来模拟一个生产销售模型的情况,代码如下:

我们运行,查看结果:

我们发现,竟然出现了库存为负数的情况;而且当count = 0时,我们在等待,结果下一刻又有了消费;

正确的情况是,生产和消费在保证各自数据安全的情况下,当生产者库存为0时,消费流程应该处于等待状态,并且当有了库存之后,要通知消费方不用再等待:

  • [_testCondition lock][_testCondition unlock]用来保证生产和消费两条线路各自的数据安全;
  • [_testCondition wait]当任务达到某个条件时(生产库存为0),将消费任务暂停,进行等待;
  • [_testCondition signal]当生产有了库存,向消费方发出信号,消费方取消wait等待状态,开始消费;

Foundation源码中锁的封装

NSLock

我们分析NSLock会发现,lockunlock方法来源于NSLockNSLocking协议,所以很多自定义的锁都有lockunlock两个方法,就是因为实现了NSLocking协议;

NSLock是来源于Foundation框架的,但是我们知道OC版本的Foundation框架是没有开源的,那么我们如何研究呢?这里我们可以使用Swift版的Foundation源码进行分析;

我们查找NSLock的实现代码:

我们发现在iOS系统中,NSLock底层是通过pthread_mutex_t来实现的;

我们看一下lockunlock两个方法的实现:

可以发现,NSLocklockunlock两个方法就是通过pthread_mutex_lock(mutex)pthread_mutex_unlock(mutex)来实现的;

NSRecursiveLock

接下来,我们分析一下NSRecursiveLock的实现:

NSRecursiveLock依然是实现了NSLocking协议,那么它的lockunlock方法呢?

其和NSLock一样,都是针对pthread_mutex_t的封装,那么就有一个疑问了:既然都是对pthread_mutex_t的封装,那么为什么NSRecursiveLock可递归锁,而NSLokc确实一把非递归锁呢?

比较两把锁的init方法,我们发现造成一把是递归锁一把是非递归锁的原因是可递归锁NSRecursiveLockinit的实现中设置了pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))

NSCondition

分析NSCondition发现,其依然是通过pthread_mutex_t实现的:

lockunlock方法:

有区别的是NSConditiondeinit方法,比起NSLockNSRecursiveLock多了cond也就是_ConditionVariablePointer的操作:

以及NSConditionwait方法的封装:

NSLockNSRecursiveLock以及NSCondition底层都是对pthread_mutex_t的封装;