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

2019-05-22  本文已影响0人  Eddiegooo

今天下午就和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

上一篇下一篇

猜你喜欢

热点阅读