GCD线程同步之信号量dispatch_semaphore_t使用起来没那么难
通过信号量可以控制不同线程任务的执行顺序和依赖关系,从而达到线程同步的目的。
1.基本用法
●dispatch_semaphore_create(value)
创建信号量,value一般情况下传0
●dispatch_semaphore_wait()
等待信号量,会对信号量减1(value - 1),当信号量 < 0 时,会阻塞当前线程,等待信号(signal),当信号量 >= 0时,会执行wait后面的代码
●dispatch_semaphore_signal()
信号量加1,当信号量 >= 0 会执行wait之后的代码。
因此dispatch_semaphore_wait()和dispatch_semaphore_signal()要成对使用。
2.示例
新建工程,点击按钮事件如下:
- (IBAction)doTestButtonTouched:(id)sender {
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 添加任务1
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"task 1");
// 任务1执行完毕,信号量 = -1 + 1 = 0,继续执行wait之后的代码
dispatch_semaphore_signal(semaphore);
});
NSLog(@"wait task 1...");
// 信号量 = 0 - 1 < 0,线程被阻塞,等待任务1执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 添加任务2
dispatch_async(globalQueue, ^{
NSLog(@"task 2");
dispatch_semaphore_signal(semaphore);
});
NSLog(@"wait task 2...");
// 阻塞线程,等待任务2结束
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 所有任务结束
NSLog(@"All tasks done!");
}
运行程序后控制台输出:
2020-02-26 21:28:53.024634+0800 TestObjC[2416:138581] wait task 1...
2020-02-26 21:28:54.029510+0800 TestObjC[2416:138702] task 1
2020-02-26 21:28:54.030083+0800 TestObjC[2416:138581] wait task 2...
2020-02-26 21:28:54.030109+0800 TestObjC[2416:138702] task 2
2020-02-26 21:28:54.030466+0800 TestObjC[2416:138581] All tasks done!
代码解释请参考其中注释。 上面这种可以用于任务2依赖任务1执行完之后再执行的场景(对异步任务做同步处理),另一种场景是两个任务互不相干,但是需要等两个任务都完成之后才能执行先关的业务代码,而两个任务谁耗时更长,谁先执行完并不确定,这种场景可以这样使用:
- (IBAction)doTestButtonTouched:(id)sender {
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 添加任务1
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"task 1");
// 任务1执行完毕,信号量 +1
dispatch_semaphore_signal(semaphore);
});
// 添加任务2
dispatch_async(globalQueue, ^{
NSLog(@"task 2");
// 任务2执行完毕,信号量 +1
dispatch_semaphore_signal(semaphore);
});
NSLog(@"wait tasks...");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"one task done!");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"The other task done!");
// 两次signal后信号量 = 0,执行接下来的代码
NSLog(@"All tasks done!");
}
运行后控制台输出:
2020-02-26 21:40:59.070483+0800 TestObjC[2624:146026] wait tasks...
2020-02-26 21:40:59.070554+0800 TestObjC[2624:146111] task 2
2020-02-26 21:40:59.070736+0800 TestObjC[2624:146026] one task done!
2020-02-26 21:41:00.070718+0800 TestObjC[2624:146113] task 1
2020-02-26 21:41:00.071000+0800 TestObjC[2624:146026] The other task done!
2020-02-26 21:41:00.071208+0800 TestObjC[2624:146026] All tasks done!
从输出结果可以看出,连续调用两次dispatch_semaphore_wait(),并不是对信号量连续两次减1,而是第一次wait减1之后,阻塞了线程,然后等到了任务2结束后的signal,此时信号量成为0,继续往下执行,wait又减1,再次阻塞,任务1执行完后signal再接着往下执行。文档中对dispatch_semaphore_wait()是这样描述的:
Decrement the counting semaphore. If the resulting value is less than zero, this function waits for a signal to occur before returning. 对信号量计数减1。如果结果值小于0,这个函数会在返回前一直等待signal
这就解释了为什么不是连续两次减1,因为第一次dispatch_semaphore_wait()函数在等待signal,还没有返回,自然无法执行第二个wait。