作者: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有两个主要应用 :
- 保持线程同步
**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这一行代码。
网友评论