美文网首页
iOS开发实战小知识——GCD使用

iOS开发实战小知识——GCD使用

作者: Eddiegooo | 来源:发表于2019-05-22 20:22 被阅读0次

    今天下午就和GCD扛上了, 索性好好了解下。 原理及源码不讲, 有兴趣的小伙伴自己去看吧。其实是我看不懂。(先跑了)。。 今天主要说说平时的使用。。。
    开篇两个问题:
    dispatch_get_global_queue 与 dispatch_get_main_queue 什么区别??
    串行和并行, 异步同步???
    Dispatch Barrierdispatch_group_wait有啥区别???
    懵逼啊。。一步一步来看看吧。。。

    GCD

    Grand Central Dispatch(GCD)是Apple推出的一套多线程解决方案,它拥有系统级的线程管理机制,开发者不需要再管理线程的生命周期,只需要关注于要执行的任务即可.

    1.dispatch_queue

    操作是在多线程上还是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了.
    开不开线程取决于同步(不具备开线程能力)还是异步。 开几条线程,取决于串行(1条)还是并行。
    所以一旦是同步执行,前面什么队列已经没区别了。主队列专门处理主线程上任务。

    dispatch_get_global_queue: //全局队列,一个并行的队列
    dispatch_get_main_queue: //主队列,主线程中的唯一队列,一个串行队列
    dispatch_get_global_queue:用于获取一个全局队列.
    dispatch_get_main_queue:该API的使用主要是在更新UI时获取dispatch_get_main_queue()并把任务提交到主队列中. main queue设置了并发数为1,即串行队列,并且将targetq指向com.apple.root.default-overcommit-priority队列。

    队列优先级有八个,分别为低、默认、高、后台以及对应的overcommit。枚举定义如下:

    enum {
        DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY = 0,                //低优先级
        DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY,         //低优先级+overcommit
        DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY,                //默认优先级
        DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY,     //默认优先级+overcommit
        DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY,                   //高优先级
        DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY,        //高优先级+overcommit
        DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY,             //后台
        DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY,  //后台+overcomit
    };
    

    自定义队列:

    这里有一个小点要注意:串行队列创建,第二个参数可以传nil、NULL。 都是串行队列。 DISPATCH_QUEUE_SERIAL是一个宏 #define DISPATCH_QUEUE_SERIAL NULL

    //串行队列 
    dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
    //并行队列
    dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
    

    当我们处理耗时操作时,比如读取数据库、请求网络数据,为了避免这些耗时操作卡住UI,可将耗时任务放到子线程中,执行完成后再通知主线程更新UI,代码示例如下:

        //耗时操作
        dispatch_async(dispatch_get_main_queue(), ^{
             //更新UI
            }); 
        });
    
    2.Dispatch Barrier

    会确保队列中先于Barrier Block提交的任务都完成后再执行它,并且执行时队列不会同步执行其它任务,等Barrier Block执行完成后再开始执行其他任务。
    当多线程并发读写同一个资源时,为了保证资源读写的正确性,可以用Barrier Block解决该问题。代码示例如下:

    //创建自定义并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.gcdTest.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //读操作
    });
    dispatch_barrier_async(queue, ^{
        //barrier block,可用于写操作
        //确保资源更新过程中不会有其他线程读取
        NSLog(@"work2");
    });
    dispatch_async(queue, ^{
        //读操作
        NSLog(@"work3");
    });
    

    这里有个需要注意也是官方文档上提到的一点,如果我们调用dispatch_barrier_async时将Barrier blocks提交到一个global queue,barrier blocks执行效果与dispatch_async()一致;只有将Barrier blocks提交到使用DISPATCH_QUEUE_CONCURRENT属性创建的并行queue时它才会表现的如同预期

    dispatch_group_wait的使用举例:

        dispatch_queue_t defultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t requestGroup = dispatch_group_create();
        
        dispatch_group_async(requestGroup, defultQueue, ^{
            sleep(2);
            NSLog(@"++++++++1111");
        });
        
        dispatch_group_async(requestGroup, defultQueue, ^{
            NSLog(@"+++++++++2222");
        });
        
        NSLog(@"++++++++please wait 1 2  \n");
        
        dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
        
        NSLog(@"++++++++++task 1 2  finished \n");
        
        dispatch_group_async(requestGroup, defultQueue, ^{
            NSLog(@"+++++++++333");
        });
        
        dispatch_group_async(requestGroup, defultQueue, ^{
            NSLog(@"+++++++++4444");
        });
        
        dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
        NSLog(@"+++++++++task 3 4 finished \n");
    

    打印结果:

    2019-05-22 20:39:46.759798+0800 GearBest[84446:1389970] ++++++++please wait 1 2
    2019-05-22 20:39:46.759844+0800 GearBest[84446:1390248] +++++++++2222
    2019-05-22 20:39:46.759839+0800 GearBest[84446:1390277] ++++++++1111
    2019-05-22 20:39:46.760051+0800 GearBest[84446:1389970] ++++++++++task 1 2  finished
    2019-05-22 20:39:46.760182+0800 GearBest[84446:1390248] +++++++++4444
    2019-05-22 20:39:46.760173+0800 GearBest[84446:1390277] +++++++++333
    2019-05-22 20:39:46.760338+0800 GearBest[84446:1389970] +++++++++task 3 4 finished
    

    以上即使加了睡眠,也是可以保证先执行12,完成之后在执行34的。

    我用Dispatch Barrier也实现了上述功能,代码如下:

        dispatch_queue_t defultQueue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(defultQueue, ^{
            sleep(2);
            NSLog(@"++++++++1111");
        });
        
        dispatch_async(defultQueue, ^{
            NSLog(@"+++++++++2222");
        });
        
        dispatch_barrier_async(defultQueue, ^{
            NSLog(@"++++++++++task 1 2  finished \n");
        });
        
        dispatch_async(defultQueue, ^{
            NSLog(@"+++++++++333");
        });
        dispatch_async(defultQueue, ^{
            NSLog(@"+++++++++4444");
        });
    
        dispatch_barrier_async(defultQueue, ^{
            NSLog(@"+++++++++task 3 4 finished \n");
        });
    

    打印结果:

    2019-05-22 20:49:51.266512+0800 GearBest[85906:1403037] +++++++++2222
    2019-05-22 20:49:53.266866+0800 GearBest[85906:1403000] ++++++++1111
    2019-05-22 20:49:53.267081+0800 GearBest[85906:1403000] ++++++++++task 1 2  finished
    2019-05-22 20:49:53.267217+0800 GearBest[85906:1403036] +++++++++4444
    2019-05-22 20:49:53.267217+0800 GearBest[85906:1403000] +++++++++333
    2019-05-22 20:49:53.267339+0800 GearBest[85906:1403036] +++++++++task 3 4 finished
    

    由上面得出的结论:Dispatch Barrier 和 dispatch_group_wait 可以实现相同的效果,只是写法不同。

    dispatch_apply . 先执行完了 在执行下面的。 栗子:
    // 注意这里mainqueue 不行。
        dispatch_queue_t globaleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(globaleQueue, ^{
            dispatch_apply(5, globaleQueue, ^(size_t index) {
                NSLog(@"11111");
            });
            NSLog(@"apply");
        });
    

    结果:

    2019-05-27 23:21:12.505513+0800 TestCode[3782:396214] 11111
    2019-05-27 23:21:12.505513+0800 TestCode[3782:396192] 11111
    2019-05-27 23:21:12.505513+0800 TestCode[3782:396194] 11111
    2019-05-27 23:21:12.505550+0800 TestCode[3782:396193] 11111
    2019-05-27 23:21:12.505768+0800 TestCode[3782:396214] 11111
    2019-05-27 23:21:12.506075+0800 TestCode[3782:396214] apply
    

    ? 直接用dispatch_group实现类似的功能:顺序执行

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"1111111");
        });
        
        dispatch_group_async(group, queue, ^{
            sleep(2);
            NSLog(@"2222222");
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"33333333");
        });
        
        dispatch_group_notify(group, queue, ^{
            NSLog(@"都执行完了啊");
        });
    

    打印结果:

    2019-05-27 22:39:23.734921+0800 TestCode[2272:267297] 33333333
    2019-05-27 22:39:23.734921+0800 TestCode[2272:267302] 1111111
    2019-05-27 22:39:25.737724+0800 TestCode[2272:267300] 2222222
    2019-05-27 22:39:25.738355+0800 TestCode[2272:267300] 都执行完了啊
    
    3. dispatch_async

    dispatch_async用来异步执行任务.dispatch_async封装调用了dispatch_async_f函数,先将block拷贝到堆上,避免block执行前被销毁,同时传入_dispatch_call_block_and_release来保证block执行后会执行Block_release。

    总结一下:dispatch_async的流程是用链表保存所有提交的block,然后在底层线程池中,依次取出block并执行;而向主队列提交block则会向主线程的Runloop发送消息并唤醒Runloo�p,接着会在回调函数中取出block并执行。
    dispatch_sync的逻辑主要是将任务放入队列,并用线程专属信号量做等待,保证每次只会有一个block在执行。

    3.2 dispatch_sync 同步按顺序执行任务。

    稍有不慎 就会死锁; 例如:

    dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"hello world?");
        });
    
    //还是死锁。。 自创建queue 这样也是死锁。。
            dispatch_async(dispatch_get_main_queue(), ^{
                dispatch_sync(dispatch_get_main_queue(), ^{
                    NSLog(@"hello world?");
                });
            });
    

    主线程中执行的block 等待主线程中执行的block执行结束。 所以死锁了。

    4. dispatch_group

    dispatch_group可以将GCD的任务合并到一个组里来管理,也可以同时监听组里所有任务的执行情况.
    实际使用情况举例:有多个网络请求,请求1、2完成拿到结果之后,才可以进行后面的请求。这时候就可以使用dispatch_group。 栗子代码:

        dispatch_group_t requestGroup = dispatch_group_create();
        dispatch_group_enter(requestGroup);
        [self loadHeadDataCompletion:^{
            //请求1
            NSLog(@"+++++++111111");
            dispatch_group_leave(requestGroup);
        }];
        
        dispatch_group_enter(requestGroup);
        [self requestRecommandSearchAdvertiseDataCompletion:^{
            //请求2
            NSLog(@"+++++++2222");
            dispatch_group_leave(requestGroup);
        }];
        
        dispatch_group_notify(requestGroup, dispatch_get_main_queue(), ^{
            //拿到了请求12的*结果*之后,在进行请求3
            [self loadHomeGoodsList];
        });
    

    dispatch_group有两个需要注意的地方:
    1、dispatch_group_enter必须在dispatch_group_leave之前出现
    2、dispatch_group_enter和dispatch_group_leave必须成对出现

    dispatch_group本质是个初始值为LONG_MAX的信号量,等待group中的任务完成其实是等待value恢复初始值。
    dispatch_group_enter和dispatch_group_leave必须成对出现。
    如果dispatch_group_enter比dispatch_group_leave多一次,则wait函数等待的
    线程不会被唤醒和注册notify的回调block不会执行;
    如果dispatch_group_leave比dispatch_group_enter多一次,则会引起崩溃。

    5.dispatch_once

    dispatch_once能保证任务只会被执行一次,即使同时多线程调用也是线程安全的。常用于创建单例、swizzeld method等功能.
    dispatch_once用原子性操作block执行完成标记位,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。

    6.dispatch_source

    dispatch_source主要用于定时器,比NSTimer精度高一点点吧。
    其他计时器参考:NSTimer
    NSTimer 到底准不准

    参考文章: Grand-Central-Dispatch

    GCD

    相关文章

      网友评论

          本文标题:iOS开发实战小知识——GCD使用

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