GCD中常用的方法

作者: 沉江小鱼 | 来源:发表于2019-10-12 16:01 被阅读0次

    主要是记录下GCD中常用的一些方法:

    1. dispatch_after
    2. dispatch group
    3. dispatch_barrier_async
    4. dispatch_suspend/dispatch_resume
    5. dispatch_once
    6. dispatch_semaphore
    1. dispatch_after

    指定时间延迟执行任务。
    经常会有这样的情况:在3秒后执行处理,这种想在指定时间后执行处理的情况,可使用dispatch_after函数来实现。
    代码如下:

    // 第一个参数指定时间 第二个参数 指定队列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 处理任务
        });
    

    注意:dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。
    因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。

    2. dispatch group

    我们希望当追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况经常会出现。
    当我们只使用一个串行队列的时候,只要将想要执行的处理全部追加到该串行队列中并在最后追加结束处理就可以了。
    但是使用并发队列时或者使用多个DispatchQueue时,就会比较复杂了,使用dispatch group可以很好的完成这个需求。

    下面看个例子,我们追加3个block到Global Dispatch Queue,这些Block如果全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block。

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSLog(@"block0");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"block1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"block2");
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"done");
        });
      
    

    执行结果如下:

    block1 block0 block2 done
    

    因为向全局并发队列追加处理,多个线程并行执行,所以追加处理的执行顺序不定,执行时会发生变化,但是执行结果的done一定时最后输出的。

    可以使用dispatch_group_wait 函数等待全部处理执行结束
    将上面代码改为:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSLog(@"block0");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"block1");
            sleep(3);
            NSLog(@"block1.1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"block2");
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"done");
        });
        
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"哈哈哈哈");
    

    输出结果:

    2019-10-12 14:57:24.287137+0800 tianran[16456:4392280] block0
    2019-10-12 14:57:24.287160+0800 tianran[16456:4392279] block1
    2019-10-12 14:57:24.287197+0800 tianran[16456:4392283] block2
    2019-10-12 14:57:27.292379+0800 tianran[16456:4392279] block1.1
    2019-10-12 14:57:27.292922+0800 tianran[16456:4392150] 哈哈哈哈
    2019-10-12 14:57:27.331627+0800 tianran[16456:4392150] done
    

    我们看到,在dispatch_group_wait 方法后输出 哈哈哈哈,这个方法阻塞了当前线程,当group中的任务处理完成时,才会继续向下执行。但是还是推荐使用dispatch_group_notify将函数追加到结束处理到主队列中。

    但是多数情况下,我们追加的任务都是网络请求,此时网络请求都是异步的,我们可以使用dispatch_group_enter dispatch_group_leave这一组函数达到相同的需求:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"block0");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"block1");
            sleep(3);
            NSLog(@"block1.1");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"block2");
            dispatch_group_leave(group);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"done");
        });
        
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"哈哈哈哈");
    

    输出:

    2019-10-12 15:06:14.502489+0800 tianran[16472:4395294] block0
    2019-10-12 15:06:14.502517+0800 tianran[16472:4395291] block1
    2019-10-12 15:06:14.502568+0800 tianran[16472:4395294] block2
    2019-10-12 15:06:17.507717+0800 tianran[16472:4395291] block1.1
    2019-10-12 15:06:17.508245+0800 tianran[16472:4395142] 哈哈哈哈
    2019-10-12 15:06:17.545181+0800 tianran[16472:4395142] done
    

    也可以达到相同的功能。

    3. dispatch_barrier_async
    • 可以保证顺序执行
    • 保证线程安全
    • 一定是自定义的并发队列才有效
    • 必须要求都在同一个队列
    • 不利于封装/不是非常优秀
    • async 不会阻塞当前线程
    • sync 会阻塞当前线程,之后的代码需要等到 sync的代码块执行完之后,才执行
      在访问数据库或文件时,如前所述,使用Serial Dispatch Queue可避免数据竞争的问题。
      写入处理确实不可以和其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。

    也就是说,为了能够高效率的访问,读取处理追加到并发队列中,写入处理在任意一个读取处理都没有执行的状态下,追加到串行队列中即可(在写入处理结束之前,读取处理不可执行)。

    我们可以使用dispatch_barrier_async函数结合并发队列一起使用,来达到这样的需求:


    代码图

    我们可以看到在blk3_for_reading处理和blk4_for_reading处理之间加入了写入处理,并将写入的内容读取blk4_for_reading处理以及之后的处理中。
    dispatch_barrier_async函数会等待追加到并发队列上的并发操作全部结束之后,再将指定的处理追加到该并发队列中。然后再由dispatch_barrier_async函数追加的处理执行完毕后,并发队列才恢复一般的行为,追加到该并发队列的处理又开始并发执行。
    如图所示:


    屏幕快照 2019-10-12 下午3.18.31.png

    dispatch_apply
    dispatch_apply函数是将dispatch_sync函数和dispatch group的关联API。该函数按指定的次数将指定的block追加到指定的dispatch queue中,并等待全部处理执行结束

    类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回。

    看下面的例子, 是在并发队列中追加任务

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"current thread - %@, index - %zu",[NSThread currentThread],index);
        });
        NSLog(@"执行完毕");
    

    输出:

    2019-10-12 15:29:33.283607+0800 tianran[16484:4400324] current thread - <NSThread: 0x281f4e680>{number = 1, name = main}, index - 1
    2019-10-12 15:29:33.283606+0800 tianran[16484:4400355] current thread - <NSThread: 0x281f10900>{number = 6, name = (null)}, index - 2
    2019-10-12 15:29:33.283635+0800 tianran[16484:4400354] current thread - <NSThread: 0x281f21b40>{number = 3, name = (null)}, index - 0
    2019-10-12 15:29:33.283688+0800 tianran[16484:4400357] current thread - <NSThread: 0x281f29980>{number = 4, name = (null)}, index - 3
    2019-10-12 15:29:33.283705+0800 tianran[16484:4400324] current thread - <NSThread: 0x281f4e680>{number = 1, name = main}, index - 5
    2019-10-12 15:29:33.283706+0800 tianran[16484:4400355] current thread - <NSThread: 0x281f10900>{number = 6, name = (null)}, index - 6
    2019-10-12 15:29:33.283698+0800 tianran[16484:4400356] current thread - <NSThread: 0x281f29940>{number = 5, name = (null)}, index - 4
    2019-10-12 15:29:33.283756+0800 tianran[16484:4400324] current thread - <NSThread: 0x281f4e680>{number = 1, name = main}, index - 8
    2019-10-12 15:29:33.283762+0800 tianran[16484:4400355] current thread - <NSThread: 0x281f10900>{number = 6, name = (null)}, index - 9
    2019-10-12 15:29:33.283771+0800 tianran[16484:4400354] current thread - <NSThread: 0x281f21b40>{number = 3, name = (null)}, index - 7
    2019-10-12 15:29:33.284204+0800 tianran[16484:4400324] 执行完毕
    

    我们看到输出的时候,分布在不同的线程中,甚至有主线程啊。但是输出结果中的 "执行完毕"必定在最后的位置上,这是因为dispatch_apply函数会等待全部处理执行结束。

    如果我们把任务追加的队列换成串行队列呢?下面是输出结果,我们可以看到跟普通的for循环没啥区别,也是在主线程中调用的。

    2019-10-12 15:33:32.526532+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 0
    2019-10-12 15:33:32.526611+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 1
    2019-10-12 15:33:32.526648+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 2
    2019-10-12 15:33:32.526680+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 3
    2019-10-12 15:33:32.526710+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 4
    2019-10-12 15:33:32.526740+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 5
    2019-10-12 15:33:32.526770+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 6
    2019-10-12 15:33:32.526866+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 7
    2019-10-12 15:33:32.527226+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 8
    2019-10-12 15:33:32.527339+0800 tianran[16488:4401459] current thread - <NSThread: 0x280976240>{number = 1, name = main}, index - 9
    2019-10-12 15:33:32.527439+0800 tianran[16488:4401459] 执行完毕
    

    如果我们从服务器获取一个数组的数据,那么我们可以使用该方法从而快速的批量字典转模型。
    代码示例如下:

    NSArray *dictArray = nil;//存放从服务器返回的字典数组
        
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
           
            dispatch_apply(dictArray.count, queue,  ^(size_t index){
                //字典转模型
                
            });
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"主线程更新");
            });
        });
    
    4. dispatch_suspend/dispatch_resume

    当追加大量处理到dispatch queue 时,在追加处理的过程中,有时候希望不执行已追加的处理,可以使用dispatch_suspend挂起dispatch queue,dispatch_resume恢复dispatch queue
    这两个函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行,而恢复则使得这些处理能够继续执行。
    我们先用串行队列举例:

    dispatch_queue_t queue = dispatch_queue_create(@"ttt", DISPATCH_QUEUE_SERIAL);
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 5; i++) {
                sleep(1);
                NSLog(@"block1 - i - %ld",i);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 5; i++) {
                sleep(1);
                NSLog(@"block2 - i - %ld",i);
            }
        });
        
        dispatch_async(queue, ^{
            for (NSInteger i = 0; i < 5; i++) {
                sleep(1);
                NSLog(@"block3 - i - %ld",i);
            }
        });
        
        sleep(3);
        dispatch_suspend(queue);
        NSLog(@"suspend and begin sleep 5 seconds");
        sleep(5);
        NSLog(@"begin resume queue");
        dispatch_resume(queue);
    

    输出:

    2019-10-12 15:52:23.856832+0800 tianran[16528:4408571] block1 - i - 0
    2019-10-12 15:52:24.858963+0800 tianran[16528:4408571] block1 - i - 1
    2019-10-12 15:52:25.852850+0800 tianran[16528:4408532] suspend and begin sleep 5 seconds
    2019-10-12 15:52:25.860057+0800 tianran[16528:4408571] block1 - i - 2
    2019-10-12 15:52:26.865483+0800 tianran[16528:4408571] block1 - i - 3
    2019-10-12 15:52:27.867016+0800 tianran[16528:4408571] block1 - i - 4
    2019-10-12 15:52:30.854289+0800 tianran[16528:4408532] begin resume queue
    2019-10-12 15:52:31.860011+0800 tianran[16528:4408571] block2 - i - 0
    2019-10-12 15:52:32.865479+0800 tianran[16528:4408571] block2 - i - 1
    2019-10-12 15:52:33.868210+0800 tianran[16528:4408571] block2 - i - 2
    2019-10-12 15:52:34.873778+0800 tianran[16528:4408571] block2 - i - 3
    2019-10-12 15:52:35.878665+0800 tianran[16528:4408571] block2 - i - 4
    2019-10-12 15:52:36.884187+0800 tianran[16528:4408571] block3 - i - 0
    2019-10-12 15:52:37.889638+0800 tianran[16528:4408571] block3 - i - 1
    2019-10-12 15:52:38.891057+0800 tianran[16528:4408571] block3 - i - 2
    2019-10-12 15:52:39.895894+0800 tianran[16528:4408571] block3 - i - 3
    2019-10-12 15:52:40.901353+0800 tianran[16528:4408571] block3 - i - 4
    

    我们看到一开始block1开始执行,当调用dispatch_suspend 之后,block1继续执行完,所以dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

    我们把队列改成并发队列再试一下:
    输出:

    2019-10-12 15:55:10.915612+0800 tianran[16532:4409557] block2 - i - 0
    2019-10-12 15:55:10.915612+0800 tianran[16532:4409561] block1 - i - 0
    2019-10-12 15:55:10.915614+0800 tianran[16532:4409559] block3 - i - 0
    2019-10-12 15:55:11.921085+0800 tianran[16532:4409557] block2 - i - 1
    2019-10-12 15:55:11.921175+0800 tianran[16532:4409561] block1 - i - 1
    2019-10-12 15:55:11.921199+0800 tianran[16532:4409559] block3 - i - 1
    2019-10-12 15:55:12.911586+0800 tianran[16532:4409506] suspend and begin sleep 5 seconds
    2019-10-12 15:55:12.926531+0800 tianran[16532:4409561] block1 - i - 2
    2019-10-12 15:55:12.926548+0800 tianran[16532:4409557] block2 - i - 2
    2019-10-12 15:55:12.926531+0800 tianran[16532:4409559] block3 - i - 2
    2019-10-12 15:55:13.931925+0800 tianran[16532:4409557] block2 - i - 3
    2019-10-12 15:55:13.931900+0800 tianran[16532:4409559] block3 - i - 3
    2019-10-12 15:55:13.931900+0800 tianran[16532:4409561] block1 - i - 3
    2019-10-12 15:55:14.932762+0800 tianran[16532:4409559] block3 - i - 4
    2019-10-12 15:55:14.937359+0800 tianran[16532:4409561] block1 - i - 4
    2019-10-12 15:55:14.937449+0800 tianran[16532:4409557] block2 - i - 4
    2019-10-12 15:55:17.913038+0800 tianran[16532:4409506] begin resume queue
    

    由于是并发队列,所以所有的任务并发执行,调用dispatch_suspend也就没什么用了,执行中的任务都会执行完。

    5. dispatch_once

    单例中,用了无数遍了。保证在应用程序执行中,只执行一次指定处理的代码。

    static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 代码
        });
    
    6. dispatch_semaphore

    这个单独写了一篇来介绍:
    https://www.jianshu.com/p/4789c32bdae7

    相关文章

      网友评论

        本文标题:GCD中常用的方法

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