美文网首页版本特性
dispatch_semaphore_t异步任务同步执行和线程加

dispatch_semaphore_t异步任务同步执行和线程加

作者: LeeRich | 来源:发表于2018-03-15 23:46 被阅读401次
作者:foolishBoy
链接:https://www.jianshu.com/p/c815ad360a2a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在UNIX环境下,多线程同步的技术有mutex、condition variable、semaphore、RW Lock、spin Lock等。在iOS平台上,可以使用dispatch_semaphore_t做线程同步。

dispatch_semaphore_t的原理类似于semaphore,与其相关的方法主要是:

dispatch_semaphore_t dispatch_semaphore_create(long value); long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore_create

创建一个新的信号量,参数value代表信号量资源池的初始数量。

value < 0, 返回NULL
value = 0, 多线程在等待某个特定线程的结束。
value > 0, 资源数量,可以由多个线程使用。

dispatch_semaphore_wait

等待资源释放。如果传入的dsema大于0,就继续向下执行,并将信号量减1;如果dsema等于0,阻塞当前线程等待资源被dispatch_semaphore_signal释放。如果等到了信号量,继续向下执行并将信号量减1,如果一直没有等到信号量,就等到timeout再继续执行。dsema不能传入NULL。

timeout表示阻塞的时间长短,有两个常量:DISPATCH_TIME_NOW表示当前,DISPATCH_TIME_FOREVER表示永远。或者自己定义一个时间:

dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);

dispatch_semaphore_signal

释放一个资源。返回值为0表示没有线程等待这个信号量;返回值非0表示唤醒一个等待这个信号量的线程。如果线程有优先级,则按照优先级顺序唤醒线程,否则随机选择线程唤醒

应用场景

dispatch_semaphore_t的应用场景很多,这里以一个异步网络请求为例。
在异步网络请求中,我们先发送网络请求,然后要等待网络结果返回再做其他事情。为了将这种异步请求改成同步的,我们可以使用dispatch_semaphore_t。

static dispatch_semaphore_t match_sema; 
-(void)asynNetWorkRequest
 { 
      /* ... 构造网络请求参数 ...
         [[IosNet sharedInstance] asyncCall:method forParam:reqData                forCallback:zuscallback forTimeout:timeoutValue]; 
      ... */ 

//创建信号量,阻塞当前线程 
static dispatch_once_t onceToken; 
  dispatch_once(&onceToken, ^{
  //信号量 :0
   match_sema = dispatch_semaphore_create(0); 
  }); 
  //信号量:0,当前线程会在这里阻塞,等待异步回调结束,signal+1
  dispatch_semaphore_wait(match_sema, DISPATCH_TIME_FOREVER); 
  //信号量为-1
} 
//请求成功 释放信号量,继续当前线程 
-(void)onCallSuccess:(NSData *)rspData 
{ 
   if (match_sema) 
  { 
    dispatch_semaphore_signal(match_sema);
   } 
 }
 //请求失败 释放信号量,继续当前线程 
-(void)onCallFail:(NSError *)errorInfo { if (match_sema)
 { 
    dispatch_semaphore_signal(match_sema);
 } }

有一点需要注意,dispatch_semaphore_wait与 异步操作不能在同一个线程中,否则异步操作会被卡住,也就不会执行到dispatch_semaphore_signal

以上例子相当于异步网络请求,同步执行


加深理解

首先,我理解的dispatch_semaphore有两个主要应用 :

  1. 保持线程同步
    **2. 为线程加锁 **

先看下相关的3个方法:

  • dispatch_semaphore_t dispatch_semaphore_create(long value):方法接收一个long类型的参数, 返回一个dispatch_semaphore_t类型的信号量,值为传入的参数
  • long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout):接收一个信号和时间值,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行
  • long dispatch_semaphore_signal(dispatch_semaphore_t dsema):使信号量加1并返回

介绍完了相关方法,下面开始介绍两种应用

保持线程同步

先看代码

dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
__block int j = 0; dispatch_async(queue, ^{ j = 100; 
dispatch_semaphore_signal(semaphore); }); 
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
NSLog(@"finish j = %zd", j);

结果输出 j = 100;

如果注掉dispatch_semaphore_wait这一行,则 j = 0;
注释: 由于是将block异步添加到一个并行队列里面,所以程序在主线
程跃过block直接到dispatch_semaphore_wait这一行,因为semaphore
信号量为0,时间值为DISPATCH_TIME_FOREVER,所以当前线程会一
直阻塞,直到block在子线程执行到dispatch_semaphore_signal,使信
号量+1,此时semaphore信号量为1了,所以程序继续往下执行。这就
保证了线程间同步了。

为线程加锁

先看代码:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); 
for (int i = 0; i < 100; i++)
 { 
dispatch_async(queue, ^{
 // 相当于加锁 
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
NSLog(@"i = %zd semaphore = %@", i, semaphore); // 相当于解锁 
dispatch_semaphore_signal(semaphore); 
}); 
}

注释:当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;如果当在线程1NSLog这一行代码还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完NSLog并执行完dispatch_semaphore_signal使信号量为1后,线程2才能解除阻塞继续住下执行。以上可以保证同时只有一个线程执行NSLog这一行代码。

相关文章

网友评论

    本文标题:dispatch_semaphore_t异步任务同步执行和线程加

    本文链接:https://www.haomeiwen.com/subject/vnwkqftx.html