iOS 开发,各种锁你了解多少?NSLock、NSCondtion、NSRecursiveLock…
回顾
在之前的一篇博客中,介绍了锁的种类,在上一篇博客中已经对@synchronized 锁进行了源码分析,还有其他的一些锁没有介绍,那么本篇博客就分析一下其他的一些锁! 
iOS底层探索之多线程(一)—进程和线程
iOS底层探索之多线程(二)—线程和锁
iOS底层探索之多线程(三)—初识GCD
iOS底层探索之多线程(四)—GCD的队列
iOS底层探索之多线程(五)—GCD不同队列源码分析
iOS底层探索之多线程(六)—GCD源码分析(sync 同步函数、async 异步函数)
iOS底层探索之多线程(七)—GCD源码分析(死锁的原因)
iOS底层探索之多线程(八)—GCD源码分析(函数的同步性、异步性、单例)
iOS底层探索之多线程(九)—GCD源码分析(栅栏函数)
iOS底层探索之多线程(十)—GCD源码分析( 信号量)
iOS底层探索之多线程(十一)—GCD源码分析(调度组)
iOS底层探索之多线程(十二)—GCD源码分析(事件源)
iOS底层探索之多线程(十三)—锁的种类你知多少?
iOS底层探索之多线程(十四)—关于@synchronized锁你了解多少?
iOS底层探索之多线程(十五)—@synchronized源码分析
1. 关于锁的介绍
1.1 锁的分类
?旋锁 :线程反复检查锁变量是否可?。由于线程在这?过程中保持执?,因此是?种忙等待 。?旦获取了?旋锁 ,线程会?直保持该锁,直?显式释放?旋锁。 ?旋锁 避免了进程上下?的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。互斥锁 :是?种?于多线程编程中,防?两条线程同时对同?公共资源(?如全局变量)进?读写 的机制。该?的通过将代码切?成?个?个的临界区?达成互斥 的作用。
属于互斥锁的有:NSLock 、pthread_mutex 、 @synchronized等
1.2 锁的归类
条件锁 :就是条件变量,当进程的某些资源要求不满?时就进?休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运?,如:NSCondition 、NSConditionLock 递归锁 :就是同?个线程可以加锁N次?不会引发死锁,如:NSRecursiveLock 、 pthread_mutex(recursive) 信号量(semaphore) :是?种更?级的同步机制,互斥锁可以说是semaphore 在仅取值0/1 时的特例。信号量可以有更多的取值空间,?来实现更加复杂的同步 ,?不单单是线程间互斥 ,如: dispatch_semaphore
锁的归类其实基本的锁就包括了三类: ?旋锁 互斥锁 读写锁 ,其他的?如条件锁 ,递归锁 ,信号量 都是上层的封装和实现!
读写锁 :读写锁实际是?种特殊的互斥锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进?读访问,写者则需要对共享资源进?写操作。
2. NSLock
- 举例1
有如下代码:
- (void)is_crash{
NSLog(@"reno");
for (int i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_testArray = [NSMutableArray array];
});
}
}
 没有加锁,多线程访问,直接奔溃了,那么现在去加锁看看结果如何呢?
 加锁的情况下,就不会奔溃,保证了线程的安全。
- 举例 2
NSLog(@"jpreno");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
});

打印结果没有任何问题,那么加个 for 循环呢? 
现在出现了,打印的数据混乱了,也就是多线程访问了,那么解决办法,就是加锁 ,那么加在哪里呢?大部分人会加在这里,如下:  那么加在上图中,会正常打印吗?现在还不得而知,现在我们去运行一下代码来看看吧!  从运行打印结果来看,数据还是错乱了,很显然NSLock 的锁的位置没有加对地方,那么正确?的加锁位置在哪里呢?请看👇:  只有把锁加在如图中位置即可解决问题,或者直接加在testMethod(10) 这个地方也是可以的。
[jp_lock lock];
testMethod(10);
[jp_lock unlock];
一般加锁,大家都喜欢和业务代码写在一起,如下:  这里一直递归,一直加锁,没有解锁,相当于死锁,只是程序还没有崩溃而已,那么为什么呢?NSLock不支持递归加锁,没有递归性。
3. NSRecursiveLock
我们还记得有个锁——NSRecursiveLock ,这个锁的性能也是还不错的,并且支持递归性,使用如下: 
NSRecursiveLock 虽然有递归性,但是不支持多线程的可递归,只运行一次就崩溃了。所以这个时候,有靓仔肯定想到了用@synchronized 这把锁了,是的,这把锁是符合递归和多线程特性的。
 通过添加@synchronized 这个锁,很完美的解决了问题,NSRecursiveLock 是解决了 NSLock 的不可递归性 ,这里使用@synchronized 是解决了NSRecursiveLock 不可多线程性 。
4. NSCondition
NSCondition 的对象实际上作为?个锁和?个线程检查器。
- 锁主要为了当检测条件时保护数据源,执?条件引发的任务;
- 线程检查器主要是根据条件决定是否继续运?线程,即线程是否被阻塞。
1:[condition lock] :?般?于多线程同时访问、修改同?个数据源,保证在同? 时间内数据源只被访问、修改?次,其他线程的命令需要在lock 外等待,只到 unlock ,才可访问 2:[condition unlock] :与lock 同时使? 3:[condition wait] :让当前线程处于等待状态 4:[condition signal] :CPU 发信号告诉线程不?在等待,可以继续执?。
现在举个生产者和消费者的例子,代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 0;
_testCondition = [[NSCondition alloc] init];
[self jp_testConditon];
}
#pragma mark -- NSCondition
- (void)jp_testConditon{
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self jp_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self jp_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self jp_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self jp_producer];
});
}
}
- (void)jp_producer{
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
}
- (void)jp_consumer{
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
}
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
}
运行结果,如下:  从运行结果,可以看到出现了负数的情况,我生产者生产的东西你都消费完了,已经没有了,你还在消费,就出现了线程不安全访问的事故了。
所以我们要保证 生产线、消费线数据的安全 ,就需要进行加锁 处理,以保证多线程安全,但这只是它们内部的得到保证了,但是它们之间存在消费关系 ,比如生产的库存没有了,不得通知,消费者进行等待 ,生产好了再通知 消费者来消费买单。
现在进行加锁改造,如下:
(void)jp_producer{
[_testCondition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_testCondition signal];
[_testCondition unlock];
}
- (void)jp_consumer{
[_testCondition lock];
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
现在再来看看,加锁之后的结果,是否安全呢?如下:
 很明显,加锁之后的打印是正常的,没有出现负数,数据是安全的!
- 如果产品不足就
[_testCondition wait] 进行等待,使得消费者停止消费 - 用
[_testCondition signal] 模拟现在有生产了,可以来消费了,向等待的线程发送信号,通知来消费
4.总结
- 多线程访问,需要保证数据的安全,可以继续加锁处理
NSLock 不支持递归加锁NSRecursiveLock 虽然有递归性,但没有多线程特性NSCondition 的对象实际上作为?个锁和?个线程检查器
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
|