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。

青山不改,绿水长流。谢谢大家!

posted @ 2022-07-10 17:37  一眼万年的星空  阅读(949)  评论(0编辑  收藏  举报