iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock

1.NSCondition

1.需求:生产者与消费者,生产者生产商品,消费者消耗商品,只有生产者产出的商品个数大于0时,消费者才能消费,否则等待生产者生产商品!
2.实现:
	- (void)viewDidLoad {
    	[super viewDidLoad];
    	self.productCount = 0;
    	[self wm_conditon];
	}

	- (void)wm_conditon {
    	_condition = [[NSCondition alloc] init];
    	//创建生产-消费者
    	for (int i = 0; i < 50; i++) {
        	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            	[self wm_producers];
        	});
        	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            	[self wm_consumers];
        	});
        
        	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            	[self wm_producers];
        	});
        	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            	[self wm_consumers];
        	});
    	}	
	}

	- (void)wm_producers{
    	[_condition lock];
    	self.productCount = self.productCount + 1;
    	NSLog(@"生产者生产一个 现有%zd个",self.productCount);
    	//发出消息
    	[_condition signal];
    	[_condition unlock];
	}

	- (void)wm_consumers{
    	// 线程安全
    	[_condition lock];
    	while (self.productCount == 0) {
        	NSLog(@"等待 count = %zd",self.productCount);
        	// 保证正常流程
        	[_condition wait];
    	}
    	//注意消费行为,要在等待条件判断之后
    	self.productCount -= 1;
    	NSLog(@"消费者消费一个 还剩%zd个",self.productCount);
    	[_condition unlock];
	}
  • NSCondition类实现了一个条件变量,该变量遵循posix条件的语义。条件对象在给定线程中充当锁和检查点。锁在测试条件并执行由条件触发的任务时保护锁内的代码。检查点行为要求在线程继续执行其任务之前,条件必须为真。当条件不为真时,线程阻塞,且一直处于阻塞状态,直到另一个线程向条件对象发出信号。

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

2.NSConditionLock

NSConditionLock是锁,⼀旦⼀个线程获得锁,其他线程⼀定等待

//表示获得锁,如果没有其他线程获得锁(不需要判断内部的condition条件) ,那它能执⾏此⾏以下代码
//如果已经有其他线程获得锁(可能是条件锁,或者⽆条件锁,有条件锁需要判断condition内部条件),则需要等待,直⾄其他线程解锁
[conditionLock lock]; 

//如果没有其他线程获得该锁,但是该锁内部的condition条件不等于A条件,此行以下代码不能获得锁,进入等待状态
//如果锁内部的condition条件等于A条件,并且没有其他线程获得该锁,则进⼊代码区
//同时设置它获得该锁,其他任何线程都将等待它的代码完成,直⾄它解锁。
[conditionLock lockWhenCondition:A条件];

//表示释放锁,同时把内部的condition条件设置为A条件
[conditionLock unlockWithCondition:A条件]; 

//表示如果被锁定(没获得锁),并超过A时间则不再阻塞线程。
//但是注意:此时返回的值是NO!
return [conditionLock lockWhenCondition:A条件 beforeDate:A时间]; 
//所谓的condition条件就是NSInteger类型的整数,内部通过整数⽐较条件
  • 看一段代码:
	- (void)wm_conditonLock {
    	// 类似于信号量
    	NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    		[conditionLock lockWhenCondition:1];
       		NSLog(@"任务 1");
       		[conditionLock unlockWithCondition:0];
    	});
    
    	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
       		[conditionLock lockWhenCondition:2];
       		NSLog(@"任务 2");
       		[conditionLock unlockWithCondition:1];
    	});
    
    	dispatch_async(dispatch_get_global_queue(0, 0), ^{
    		[conditionLock lock];
    		NSLog(@"任务 3");
    		[conditionLock unlock];
    	});
	}
	//打印结果是什么?
	//321或213 
	/**
	分析:
	任务1调⽤[NSConditionLock lockWhenCondition:],此时此刻因为不满⾜当前条件,
	所以会进⼊ waiting 状态,当前进⼊到 waiting 时,会释放当前的互斥锁。

	任务2调用[NSConditionLock lockWhenCondition:],因为满⾜条件值,所以任务2会打印,
	打印完成后会调⽤[NSConditionLock unlockWithCondition:],这个时候将 value 设置为1,并发送广播boradcast, 
	此时任务1接收到当前的信号,唤醒执⾏并打印。所以1在2的后面打印!
	
	任务3调⽤[NSConditionLock lock:],本质上是调⽤[NSConditionLock lockBeforeDate:],
	这⾥不需要⽐对条件值,所以任务3会打印
	
	[NSConditionLock lockWhenCondition:]:这⾥会根据condition内部条件值和传入的Value值进⾏对⽐,
	如果不相等,这⾥就会阻塞,进⼊线程池,否则的话就继续代码执⾏
	
	[NSConditionLock unlockWithCondition:]:这⾥会先将condition内部的条件值更改为当前的value值,
	然后进⾏⼴播,唤醒其他获取条件锁内部条件值的为value的线程。
	*/

3.NSConditionLock探究

  • 运行我们上面NSConditionLock的代码
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第1张图片

  • 我们添加断点,运行,调试Xcode,选择Debug->Debug Workflow ->Always Show Disassembly,去看汇编的调用流程!
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第2张图片

  • 添加-[NSConditionLock lockWhenCondition:]符号断点,同样的,我们可以添加-[NSConditionLock unlockWithCondition:]符号断点!现在过掉上一个断点:
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第3张图片

  • 打印了一堆和NSDate相关的,那我们猜想这个应该是和时间相关的!去看了一下文档,搜索一下lockWhenCondition,文档给出了相关的函数有两个: 1.lockWhenCondition:beforeDate: 2.unlockWithCondition:,第二个我们知道,那第一个也正好和Date相关,那是不是苹果在我们调用lockWhenCondition:方法时,按照苹果的习惯,工厂模式去调用了lockWhenCondition:beforeDate:方法!现在我们去打印一下第二objc_msgSend:
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第4张图片

  • 上面已经看到,却是去调用了lockWhenCondition:beforeDate:这个方法,那就相同思路,再去添加一个符号断点-[NSConditionLock lockWhenCondition:beforeDate:],过掉lockWhenCondition:断点:
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第5张图片

  • 上面的打印能够看出,NSConditionLock是对NSCondition的封装!那这里是封装了NSCondition,加了一把锁,但是条件在哪处理的呢?
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第6张图片

  • 不相等,则调用了waitUntilDate:方法,表示线程进入等待状态!那么我们现在把断点过掉,进到任务二的流程去看看compare x8和x21的比较,因为前面的流程都一样:
    iOS底层原理篇(十五) ---- 条件锁NSCondition&NSConditionLock_第7张图片

  • 至此,整个流程算是完成一个闭环!

  • 其实在swift源码中,我们也能看到这样一个逻辑!这里使用这种方法来探究整个流程,就是为了学习这样一种探析方法,当我们没有源码的时候也能去试着探析底层原理及流程!

你可能感兴趣的:(iOS底层原理)