盒子
盒子
Posts List
  1. 前言
  2. suspend/resume
  3. dispatch_barrier
  4. 总结

iOS GCD(Grand Central Dispatch)的使用(三)

作者Talent•C
转载请注明出处

前言

很抱歉,最近比较忙更新的有点晚了;前几篇文章我们介绍了GCD的概念、理论基础及基础使用,我们这篇文章主要介绍一下 dispatch_suspend (队列挂起)、 dispatch_resume (队列恢复)、 dispatch_barrier(栅栏) 的使用。

suspend/resume

  • suspend: 通过 dispatch_suspend() 函数实现队列的”挂起”,使队列暂停工作。但是这里的“挂起”,并不能立即停止队列上正在运行的block;
  • resume: dispatch_resume() 函数恢复队列,是队列继续工作。

注意: dispatch_suspenddispatch_resume 要成对出现。

原因:

我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当计数大于0时,队列仍然被挂起,因此必须平衡每个dispatch_suspend调用与匹配的dispatch_resume调用。
摘自:Apple 开发者文档

现在我们来实现一个小功能:一个异步串行队列,向其中添加5任务,延迟7秒暂停这个队列,然后再延迟5秒回复队列.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//队列挂起/恢复
dispatch_queue_t talentQueue = dispatch_queue_create("talentQueue.test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 5; i++) {
dispatch_async(talentQueue, ^{
NSString *taskString = [NSString stringWithFormat:@"任务%d",i];
NSLog(@"%@开始执行 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
sleep(3);
NSLog(@"%@执行完毕 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
});
}
NSLog(@"任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"队列延迟7秒挂起 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");
dispatch_suspend(talentQueue);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"队列挂起后延迟5秒恢复 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");
dispatch_resume(talentQueue);
});
});

输出结果:

2017-05-02 10:46:59.639 GCD-Test[77894:5941123] 任务添加完毕 是否主线程:YES
2017-05-02 10:46:59.639 GCD-Test[77894:5941325] 任务0开始执行 是否主线程:NO
2017-05-02 10:47:02.643 GCD-Test[77894:5941325] 任务0执行完毕 是否主线程:NO
2017-05-02 10:47:02.644 GCD-Test[77894:5941325] 任务1开始执行 是否主线程:NO
2017-05-02 10:47:05.644 GCD-Test[77894:5941325] 任务1执行完毕 是否主线程:NO
2017-05-02 10:47:05.644 GCD-Test[77894:5941325] 任务2开始执行 是否主线程:NO
2017-05-02 10:47:06.640 GCD-Test[77894:5941123] 队列延迟7秒挂起 是否主线程:YES
2017-05-02 10:47:08.649 GCD-Test[77894:5941325] 任务2执行完毕 是否主线程:NO
2017-05-02 10:47:11.640 GCD-Test[77894:5941123] 队列挂起后延迟5秒恢复 是否主线程:YES
2017-05-02 10:47:11.652 GCD-Test[77894:5941325] 任务3开始执行 是否主线程:NO
2017-05-02 10:47:14.664 GCD-Test[77894:5941325] 任务3执行完毕 是否主线程:NO
2017-05-02 10:47:14.664 GCD-Test[77894:5941325] 任务4开始执行 是否主线程:NO
2017-05-02 10:47:17.667 GCD-Test[77894:5941325] 任务4执行完毕 是否主线程:NO

通过打印结果我们可以验证当调用 dispatch_suspend(talentQueue) “挂起”队列 talentQueue 后已经开始执行的任务不会挂起,而未开始的任务可以挂起。

dispatch_barrier

官方解释:

一个 dispatch barrier 允许在一个并发队列中创建一个同步点。当在并发队列中遇到一个 barrier, 他会延迟执行 barrierblock ,等待所有在 barrier 之前提交的 blocks 执行结束。 这时,barrier block 自己开始执行。 之后, 队列继续正常的执行操作。

调用这个函数总是在 barrier block 被提交之后立即返回,不会等到 block被执行。当 barrier block 到并发队列的最前端,他不会立即执行。相反,队列会等到所有当前正在执行的 blocks结束执行。到这时,barrier 才开始自己执行。所有在 barrier block 之后提交的 blocks 会等到 barrier block 结束之后才执行。

这里指定的并发队列应该是自己通过 dispatch_queue_create 函数创建的。如果你传的是一个串行队列或者全局并发队列,这个函数等同于dispatch_async函数。
摘自:Apple 开发者文档

相关API:

1
2
3
4
5
6
//同步
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
//异步
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

dispatch_barrier_syncdispatch_barrier_async 的区别
相同点:

  • 等待在它前面插入队列的任务先执行完。
  • 等待他们自己的任务执行完再执行后面的任务。
    不同点:
  • dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们。
  • dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务。
    代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    dispatch_queue_t talentQueue_bar = dispatch_queue_create("talentQueue.barrier.test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5; i ++) {
    if (i == 2) {
    dispatch_barrier_async(talentQueue_bar, ^{
    NSString *taskString = [NSString stringWithFormat:@"任务%d barrier任务",i];
    NSLog(@"%@开始执行 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
    //这里使用 GCD 的计时器代替 sleep(3) 可以更加明显的看出效果
    __block int count_t = 0;
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    uint64_t interval = (uint64_t)(0.5 * NSEC_PER_SEC);
    dispatch_source_set_timer(timer, start, interval, 0);
    dispatch_source_set_event_handler(timer, ^{
    CGFloat p = count_t/10.0;
    p = p>1?1:p;
    if (p == 1) {
    //取消定时器
    dispatch_cancel(timer);
    NSLog(@"%@执行完毕 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
    }
    count_t++;
    });
    // 启动定时器
    dispatch_resume(timer);
    });
    }else {
    dispatch_async(talentQueue_bar, ^{
    NSString *taskString = [NSString stringWithFormat:@"任务%d",i];
    NSLog(@"%@开始执行 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
    sleep(3);
    NSLog(@"%@执行完毕 是否主线程:%@",taskString,[NSThread currentThread].isMainThread?@"YES":@"NO");
    });
    }
    }
    NSLog(@"任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");

结果打印:
2017-05-02 12:11:11.232 GCD-Test[80817:5993383] 任务添加完毕 是否主线程:YES
2017-05-02 12:11:11.232 GCD-Test[80817:5993420] 任务0开始执行 是否主线程:NO
2017-05-02 12:11:11.232 GCD-Test[80817:5993422] 任务1开始执行 是否主线程:NO
2017-05-02 12:11:14.233 GCD-Test[80817:5993420] 任务0执行完毕 是否主线程:NO
2017-05-02 12:11:14.237 GCD-Test[80817:5993422] 任务1执行完毕 是否主线程:NO
2017-05-02 12:11:14.237 GCD-Test[80817:5993422] 任务2 barrier任务开始执行 是否主线程:NO
2017-05-02 12:11:14.238 GCD-Test[80817:5993422] 任务3开始执行 是否主线程:NO
2017-05-02 12:11:14.238 GCD-Test[80817:5993420] 任务4开始执行 是否主线程:NO
2017-05-02 12:11:17.238 GCD-Test[80817:5993420] 任务4执行完毕 是否主线程:NO
2017-05-02 12:11:17.238 GCD-Test[80817:5993422] 任务3执行完毕 是否主线程:NO
2017-05-02 12:11:20.238 GCD-Test[80817:5993383] 任务2 barrier任务执行完毕 是否主线程:YES

从打印结果可以看出来等待任务0 和 任务1 执行完毕后开始执行 barrier任务 。但是因为这里用但是dispatch_barrier_async是异步的,所以不会等待 barrier任务 执行完毕再开启下一个任务,如果希望barrier任务 执行完毕后再开始其他任务则使用dispatch_barrier_sync

barrier 相关API

1
2
3
4
5
6
//在指定的通道上设置屏障操作。
void dispatch_io_barrier(dispatch_io_t channel, dispatch_block_t barrier);
void dispatch_assert_queue_barrier(dispatch_queue_t queue);
//下面是两个宏
dispatch_assert_queue_barrier_debug(q) //此宏实际上调用的是dispatch_assert_queue_barrier
dispatch_compiler_barrier();

总结

我们在使用dispatch_suspenddispatch_resume要注意他们两个要成对出现以避免队列不执行,永远挂起的情况;在使用dispatch_barrier时要注意dispatch_barrier的函数特性注意加入队列的位置及时机。

支持一下
扫一扫,支持Talent•C