ios底层探索之多线程(十六)——锁分析(nslocknscondtionnsrecursivelocknscondition)(代码片段)

卡卡西Sensei 卡卡西Sensei     2022-12-25     789

关键词:

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 锁的归类

  • 条件锁:就是条件变量,当进程的某些资源要求不满⾜时就进⼊休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运⾏,如:NSConditionNSConditionLock
  • 递归锁:就是同⼀个线程可以加锁N次⽽不会引发死锁,如:NSRecursiveLockpthread_mutex(recursive)
  • 信号量(semaphore):是⼀种更⾼级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,⽤来实现更加复杂的同步,⽽不单单是线程间互斥,如: dispatch_semaphore

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

  • 读写锁:读写锁实际是⼀种特殊的互斥锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进⾏读访问,写者则需要对共享资源进⾏写操作。

2. NSLock

  1. 举例1

有如下代码:

- (void)is_crash
    NSLog(@"reno");
    for (int i = 0; i < 10000; i++) 
        dispatch_async(dispatch_get_global_queue(0, 0), ^
            _testArray = [NSMutableArray array];
        );
    


  • 不加锁的时候运行


没有加锁,多线程访问,直接奔溃了,那么现在去加锁看看结果如何呢?

  • 加锁


加锁的情况下,就不会奔溃,保证了线程的安全。

  1. 举例 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 的对象实际上作为⼀个锁和⼀个线程检查器

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

ios底层探索之多线程(十三)—锁的种类你知多少?(代码片段)

...#xff1f;从本篇博客开始将对锁的相关内容进行分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识GCDiOS底层探索之多线程(四)—GCD的队列iOS底层探索之多线程(五)—GCD... 查看详情

ios底层探索之多线程(十七)——通过swift的foundation源码分析锁(nslocknsconditionnsrecursivelock)(代码片段)

...篇博客就继续分析锁,从Foundation源码分析锁!iOS底层探索之多线程(一)—进程和线程 查看详情

ios底层探索之多线程(十四)—关于@synchronized锁你了解多少?(代码片段)

...f;对于锁你又了解多少?锁的原理你又知道吗?iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识GCDiOS底层探索之多线程(四)—GCD的队列iOS底层探索之多线程(五)—GCD... 查看详情

ios底层探索之多线程—gcd源码分析(栅栏函数)(代码片段)

...顾在上篇博客已经对GCD函数的同步性/异步性还有单例的底层源码,作了详细的分析,那么本篇博客将对栅栏函数,调度组等底层源码进行探索分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)... 查看详情

ios底层探索之多线程—gcd源码分析(函数的同步性异步性单例)(代码片段)

...博客已经对GCD的sync同步函数产生死锁的情况,进行了底层的源码探索分析,那么本篇博客继续源码的探索分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初... 查看详情

ios底层探索之多线程—gcd源码分析(信号量dispatch_semaphore_t)(代码片段)

...栅栏函数做了一个基本介绍,还有应用的举例并且对底层源码进行了分析,本篇博客将对信号量进行探索分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识... 查看详情

ios底层探索之多线程—gcd源码分析(事件源dispatch_source)(代码片段)

...博客已经对GCD的调度组做了介绍和举例应用,还有对底层源码的分析,那么本篇博客将对事件源dispatch_source进行分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)... 查看详情

ios底层探索之多线程—gcd不同队列源码分析(代码片段)

...,那么本篇博客将继续介绍GCD的队列和源码分析。iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识GCDiOS底层探索之多线程(四)—GCD的队列1.主队列分析查看主队列的api如... 查看详情

ios底层探索之多线程—gcd源码分析(调度组)(代码片段)

...已经对GCD的信号量做了一个介绍和举例应用,还有对底层源码的分析,那么本篇博客看苹果工程师,如何巧妙封装调度组,看完底层源码直呼好家伙,真是妙啊!!!iOS底层探索之多线程(一)—进程... 查看详情

ios底层探索之多线程(十八)——锁篇章的完结篇(手把手两种方式带你实现一个读写锁!)(代码片段)

...,那么本篇博将手把手带你实现一个读写锁!iOS底层探索之多线程(一)—进程和线程iOS 查看详情

ios底层探索之多线程—初识gcd(代码片段)

...用最多的还是GCD,那么从本篇开始讲陆续介绍GCD。iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁1.什么是GCDGCD定义GrandCenterDispatch简称GCD,是苹果公司开发的技 查看详情

ios底层探索之多线程—gcd源码分析(死锁的原因)(代码片段)

回顾在上篇博客已经对GCD的sync同步函数、async异步函数进行了源码的分析,那么本篇博客继续源码的探索分析!1.补充sync和async的区别是否可以开启新的线程执行任务任务的回调是否具有异步行、同步性是否产生死锁问题... 查看详情

ios底层探索之多线程—gcd源码分析(sync同步函数async异步函数)(代码片段)

回顾在上篇博客对GCD的不同的队列继续了底层的源码探索分析,那么本篇博客将继续对GCD的函数继续源码分析。1.sync同步函数我们都知道GCD底层是用C写的,封装了block函数来执行添加的任务,那么这个block底层是如何... 查看详情

ios底层探索之多线程—gcd的队列(代码片段)

...的认识,那么本篇博客将继续介绍GCD的相关知识。iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识GCD1.不同队列举例主队列添加同步任务看看下面这个例子🌰//主队... 查看详情

ios底层探索之多线程—进程和线程(代码片段)

前言在iOS的面试中多线程是经常被问到的,多线程也是一个难点,很多面试者平时用的不多,因此很难回答到点子上,那么本篇博客就对多线程进行探索和分析。1.进程和线程什么是进程进程是指在系统中正在运... 查看详情

ios开发底层之多线程探索-19(代码片段)

文章目录前言一、进程与线程?1.进程2.线程3.进程与线程的关系二、多线程1.多线程优点2.多线程缺点3.线程的生命周期4.线程池的饱和策略RejectedExecutionHandler接口5.优先级翻转(IOvccpu优先级提升)6.优先级的影响因素三.多线程下... 查看详情

ios开发底层之多线程探索-19(代码片段)

文章目录前言一、进程与线程?1.进程2.线程3.进程与线程的关系二、多线程1.多线程优点2.多线程缺点3.线程的生命周期4.线程池的饱和策略RejectedExecutionHandler接口5.优先级翻转(IOvccpu优先级提升)6.优先级的影响因素三.多线程下... 查看详情

ios底层探索之多线程—线程和锁(代码片段)

回顾在上一篇博客中,我们已经对进程和线程有了一定的了解了,那么本次博客将继续讲解!1.线程的生命周期在程序开发中有个名词——生命周期,我们都知道APP有生命周期,那么线程的生命周期是什么样子... 查看详情