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

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

GCD

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

1.dispatch_queue

操做是在多线程上仍是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了. 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。枚举定义以下:bash

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
};
复制代码

自定义队列:网络

//串行队列
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时它才会表现的如同预期app

dispatch_group_wait的使用举例:less

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发送消息并唤醒Runloop,接着会在回调函数中取出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的任务合并到一个组里来管理,也能够同时监听组里全部任务的执行状况. 实际使用状况举例:有多个网络请求,请求一、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有两个须要注意的地方: 一、dispatch_group_enter必须在dispatch_group_leave以前出现 二、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

相关文章
相关标签/搜索