iOS开发实战小知识——GCD使用
今天下午就和GCD扛上了, 索性好好了解下。 原理及源码不讲, 有兴趣的小伙伴自己去看吧。其实是我看不懂。(先跑了)。。 今天主要说说平时的使用。。。
开篇两个问题:
dispatch_get_global_queue 与 dispatch_get_main_queue 什么区别??
串行和并行, 异步同步???
Dispatch Barrier
与dispatch_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 到底准不准