其次篇:GCD

2019-10-03 00:54 来源:未知

图片 1OC高端编制程序:内部存款和储蓄器管理、block、GCD

概念

目录

一、dispatch_async和dispatch_sync
二、串行队列和相互队列、主队列和大局队列
三、dispatch_async、dispatch_sync和串行队列、并行队列的两种组成举例,来证实一下前两局地的论争
四、死锁
五、GCD的其他常用API
1、dispatch_once
2、dispatch_after
3、dispatch_group
4、dispatch_barrier_async
5、dispatch_apply

基础

https://github.com/ChenYilong/ParseSourceCodeStudy/blob/master/01_Parse的三十二线程管理思路/GCD扫除文盲篇.md

串行队列:同时队列中唯有五个职务在执行,每一种义务唯有在前三个职分奉行到位后才干发轫实践。
你不明了在贰个Block(职责)实行实现到下三个Block(职务)开端实行之间的这段日申时间是多少长度

交互队列:你独一能担保的是,那几个职分会遵纪守法被增多的逐条同始施行。可是义务可以以另外顺序完毕
你不明了在实践下一个职分是从几时开头,可能说任性时刻有四个Block(任务)运营,那么些完全都以决意于GCD。

大局队列:不要与 barrier 栅栏方法搭配使用, barrier 独有与自定义的竞相队列一同行使,技艺让 barrier 达到大家所企盼的栅栏成效。与 串行队列或然 global 队列 一齐利用,barrier 的显现会和 dispatch_sync 方法一致

主队列:不可能与 sync 同步方法搭配使用,会促成死循环

前面一篇大家说起NSThread是面向线程来促成二十八线程编制程序的,供给我们和睦来写一些线程管理的代码,那样的话一方面大家开拓起来代码量会多不简洁,另一方面那样的线程管理成效并不高,再者万一大家有九二十一个职责供给并行实施也许说并发实施,这NSThread是平昔不章程重用以前的线程的,因而就须要创立玖17个线程,那太吓人了,GCD能够援引线程。

多少个有名的行列

同步串行队列
不会新建线程,依旧在日前线程上。
恍仿佛步锁,是同步锁的代表方案

异步并发队列
会新建线程,能够开多条线程。
iOS7-SDK 时代平时是5、6条, iOS8-SDK 现在能够50、60条。

异步串行队列
会新建线程,只开一条线程
一条线程就够了
老是使用 createDispatch 方法就能够新建一条线程,多次调用该措施,会创制多条线程,多条线程间会并行实施

一齐并行队列
不会新建线程,依旧在当下线程上

而GCD则提供了非常轻便的十六线程完毕方案,API十二分轻巧,只需求把大家想要实行的职分放在八个block里,并分配到钦赐的行列,系统就会帮大家创立线程达成八线程开辟,也正是说线程的田间管理交给了系统基本来做,换句话说无论大家手动如何编写线程管理代码也不及系统基本来管理,因而GCD的进行效能要比NSThread高很多。其它GCD也提供了有些别样好实惠的API来援救大家成功二十四线程的支出。

串行队列中的同步与异步的区分

串行队列能确定保障顺序施行义务,他们四个的唯一分裂在于dispatch_sync只会在 block 完全实施完之后回来,dispatch_async 不可能确认保证会在 block 完全实践完事后回到,独一能分明的是会在被增多到queue 队列后重返。

下边包车型客车代码:

dispatch_async(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_async(_serialQueue, ^{ printf("3"); });
printf("4");

唯恐会打字与印刷 2413 、 2143 、 1234 ,但有一些是足以分明的: 1 总是在 3 此前。

可是下边的代码:

dispatch_sync(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_sync(_serialQueue, ^{ printf("3"); });
printf("4");

总会打字与印刷:1234

当大家来看dispatch_async和dispatch_sync时,大家只须要从两上边深入分析它俩就能够了,即能否开辟线程和是否会阻塞当前线程

dispatch_async能开垦线程,并且不会堵塞当前线程。

dispatch_sync不可能开发线程,何况会堵塞当前线程。

因此,抓住dispatch_async和dispatch_sync的那多少个特色,大家精通如果我们想用GCD实现多线程开发,就必须使用dispatch_async来分配任务,使用dispatch_sync是实现不了多线程开发的,当今自己权且不知道dispatch_sync存在的含义,未有找到它的其实使用场景。

队列和职责

下边大家来具体说一下。

队列

GCD有三种队列类型:串行队列,并发队列,主队列
串行队列:假诺你创建了4个串行队列,每三个行列在同不常候都只进行多个职责,对那多个任务的话,他们是互为独立且并发实行的。

并发队列:并发队列会基于系统负荷来符合地选用并发实践那几个职责。并发队萨拉热窝常指的就是全局队列(Global queue),进度中设有多个全局队列:高、中(暗中认可)、低、后台多个先行级队列,能够调用dispatch_get_global_queue函数传入优先级来拜谒队列。

主队列:与主线程功用同样。提交至main queue的职务会在主线程中施行。这几个行列中的职分壹遍只可以进行一个。它能担保全数的职分都在主线程执行,而主线程是独一可用于更新 UI 的线程。

并行试行的类别数量受到内核数的限量,无法真正成功大量行列并行实践;举个例子,对于相互队列中的全局队列来讲,其存在优先级关系,施行的时候也会按部就班其优先顺序,并不是并行。

1、dispatch_async
// 异步地分配任务dispatch_async(someQueue, ^{ // 任务...});

第一我们要理解:dispatch_async是将三个任务异步地抽成到内定的连串中,所谓异步是指这个函数在把一个任务分配到指定的队列后,会立马返回。说得更直接一点正是dispatch_async这个函数的功能 = 把任务分配到队列中,这一个函数的作用奉行完,上边包车型地铁代码就能够实行,因而使用dispatch_async分配任务不会阻塞调用dispatch_async函数的线程,不打断的原故正是dispatch_async根本不爱抚它所分配的那多少个职分实行不进行,它分配完就大功告成了。

说不上我们要精晓:只要你使用dispatch_async来分配任务,GCD都会开辟线程来执行这些任务,当队列是串行队列时,GCD只开荒三个线程,当队列是互相队列时,GCD开采多少个线程。也唯有在运用dispatch_async分配职分的意况下,串行队里和互相队列才有同理可得的差距。

任务

协助实行职分,使用dispatch_sync将职责出席队列。将共同任务参预串行队列,会挨个推行,平日不那样做,何况在贰个职务未停止时调起其余同步职责会死锁。将一齐职责出席并行队列,会相继实施,不过也没怎么含义。

异步职务,使用dispatch_async将职务到场队列。将异步职责参与串行队列,会相继执行,何况不会油然则生死锁难点。将异步职分到场并行队列,会并行实行八个义务,那也是大家最常用的一种办法。

2、dispatch_sync
// 同步地分配任务dispatch_sync(someQueue, ^{ // 任务...});

首先我们要领会:dispatch_sync是将一个职务同步地分红到钦点的队列中,所谓同步是指这个函数在把一个任务分配到指定的队列后,不会立马返回,而是要挂在这儿等,等它所分配的这个任务执行完毕后才返回。说得更直接一点正是dispatch_sync这个函数的功能 = 把任务分配到队列中 + 等待这个任务执行完毕,那一个函数的效果不试行完,上边包车型的士代码就甭想实施,因而使用dispatch_sync分配任务会阻塞调用dispatch_sync函数的线程,阻塞的案由正是dispatch_sync函数要等它所分配的不胜任务推行完结。

协理大家要了解:只要你使用dispatch_sync来分配任务,那么不管你分配到的队列是串行队列还是并行队列,GCD都不会开辟线程,而是把队列里的任务(其实只有一个,见细想)放在调用dispatch_sync函数的线程上来执行。细想一下,dispatch_sync是分配一个任务到行列(无论是串行队列依然并行队列)中,就立即拿来施行,推行完就从队列里移除,由此大家也就来看这种处境下队列其实是从未存留意义的,串行队列和互动队列在这种情景下没什么差异,因为dispatch_sync总是三个职责三个职务地入队列-->试行完-->骑行列啊,而队列的真的指标是储存七个职务嘛。

队列是一种数据结构,在GCD里它是用来囤积职责的,它个中的天职会按照添加的顺序,先进先出地启动执行(注意这里职分仅仅是按梯次运行推行,而不必然会按顺序推行完结,因而下文中说起的“按梯次实施”是指“按梯次实行达成”),而队列又能够分成串行队列和交互队列三种,另外GCD还为大家提供了八个特别的行列主队列和全局队列

当大家看看串行队列和相互队列时,也是只供给从双方面剖判它俩就足以了,即GCD会为队列会开辟几个线程和队列里的任务是否按顺序执行,当然它俩的剖判其实要依附大家运用的是dispatch_async还是dispatch_sync的。

当大家使用dispatch_async时,GCD只会为串行队列开荒二个线程,队列里的职分按梯次实施;GCD会为并行队列会开发多个线程,队列里的职责不按梯次实施,大家也不能够调整任务的举行各样。

当大家运用dispatch_sync时,无论是串行队列照旧并行队列GCD都不会开荒线程,队列里的职务会被增多到调用dispatch_sync函数的线程里实施,当然职责是按梯次实施的。

之所以,抓住串行队列和互相队列的这两本性状,大家就会遵照自身实际的必要来调控到底使用串行队列还是并行队列了,如果我们的多个任务必须按顺序执行或者我们不想让多个任务并行执行,那就必须得用串行队列;而如果我们对任务的执行顺序没有要求,那就大可以使用并行队列,毕竟并行队列是多个任务并行执行,程序的执行效率会更高一些。

下边大家来具体说一下。

1.一些GCD的函数

1、串行队列(serial queue)
// 串行队列:第一个参数是该队列的唯一标识符,第二个参数是队列的类型dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);

率先大家要领会:GCD只会为串行队列开采一个线程来挨个实践队列之中的职务。

说不上大家要精晓:串行队列里的职责是按梯次运转实施的,何况也是按梯次实施完成的,因为串行队列里唯有上三个职务运行实行并实施完结,下叁个任务才会运转实践。总的来讲,串行队列里的天职是按梯次实施的。

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, ^{
    // 异步任务1
});

dispatch_group_async(group, queue, ^{
    // 异步任务2
});

// 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式

// 方式1(不好,会卡住当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...

// 方式2(比较好)
dispatch_group_notify(group, mainQueue, ^{
    // 任务完成后,在主队列中做一些操作
    ...
});

上述的一种办法,能够适用于本人维护的一对异步职责的一路难点;不过对于早就封装好的一些库,举例AFNetworking等,大家不得到其异步职分的行列,这里能够经过一种计数的不二秘诀调节职分间一块,上面为消除单分界面多接口的一种情势。

// 计数+1
dispatch_group_enter(group);
[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...

    // 计数-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...

    // 计数-1
    dispatch_group_leave(group);
}];

// 计数+1
dispatch_group_enter(group);
[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...

    // 计数-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...

    // 计数-1
    dispatch_group_leave(group);
}];

// 其实用计数的说法可能不太对,但是就这么理解吧。会在计数为0的时候执行dispatch_group_notify的任务。
dispatch_group_notify(group, mainQueue, ^{
    // 一般为回主队列刷新UI
    ...
});
2、并行队列(concurrent queue)
// 并行队列:第一个参数是该队列的唯一标识符,第二个参数是队列的类型dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

率先大家要精晓:GCD会为并行队列开发三个线程来还要实践队列里的义务,至于实际开垦多少个,那些决意于那时CPU的核数和负荷情形。

说不上大家要知道:并行队列里的任务是按顺序运行实践的,但不必然按梯次施行实现,因为并行队列并不会等待上贰个任务施行完成后才运营下二个职务,而是在上三个职务运行今后就立即运行下七个职务,而且一旦上四个任务的职业量大于下三个职务的专门的职业量,大家也无从保险上四个职务一定比下贰个职务先举行完结。总的来讲,并行队列里的职分不是按梯次实践的,

2.dispatch_barrier_async

// dispatch_barrier_async的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 任务1
    ...
});
dispatch_async(queue, ^{
    // 任务2
    ...
});
dispatch_async(queue, ^{
    // 任务3
    ...
});
dispatch_barrier_async(queue, ^{
    // 任务4
    ...
});
dispatch_async(queue, ^{
    // 任务5
    ...
});
dispatch_async(queue, ^{
    // 任务6
    ...
});

和dispatch_group类似,dispatch_barrier也是异步任务间的一种共同情势,能够在比如文件的读写操作时行使,保障读操作的准头。另外,有几许亟需小心,dispatch_barrier_sync和dispatch_barrier_async只在大团结创办的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果同样。

3、主队列(main queue)
// 主队列dispatch_queue_t mainQueue = dispatch_get_main_queue();

主队列是三个串行队列,大家增多到主队列里的天职都会被平放主线程中去施行。(主线程创造的相同的时间,主队列也随后创造实现了,而且系统自动把主线程中要实施的职责都加上到主队列中)

重临主线程比如:

dispatch_async(dispatch_get_main_queue(), ^{ // 刷新UI或做其它操作...});

3.dispatch_apply

// for循环做一些事情,输出0123456789
for (int i = 0; i < 10; i ++) {
    NSLog(@"%d", i);
}

// dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
*
*  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
*         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
*
*  @param 10    指定重复次数  指定10次
*  @param queue 追加对象的Dispatch Queue
*  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});

因为dispatch_apply并行的运营机制,效用平常快于for循环的类串行机制(在for贰遍巡回中的管理职责过多时差异十分的大)。比方那足以用来拉取网络数据后提前算出各样控件的高低,防止绘制时总计,升高表单滑动流畅性,假如用for循环,耗时比较多,何况每一个表单的多寡未有正视关系,所以用dispatch_apply比较好。

4、全局队列(global queue)
// 全局队列dispatch_queue_t globalQueue = dispatch_get_global_queue;

全局队列是一个相互队列,平常意况下一旦我们要选择并行队列,直接用全局队列就足以了,没须要再特别去创建一个相互队列,除非项目中要动用到多个互相队列。

4.dispatch_suspend和dispatch_resume

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue);  //恢复队列queue

那八个函数不会潜濡默化到行列中一度实行的天职,队列暂停后,已经增添到队列中但还未曾试行的任务不会施行,直到队列被恢复生机。

1、dispatch_async + 串行队列

我们把5个任务异步地分配到1个串行队列里:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; NSLog;}

输出:

111222<NSThread: 0x6000019e2f40>{number = 3, name = }1<NSThread: 0x6000019e2f40>{number = 3, name = }2<NSThread: 0x6000019e2f40>{number = 3, name = }3<NSThread: 0x6000019e2f40>{number = 3, name = }4<NSThread: 0x6000019e2f40>{number = 3, name = }5

解析一下:

  • 作者们看出这里有着的任务都以选取dispatch_async来分配的,而dispatch_async不会堵塞调用它的线程,所以打印完“111”后就一向打字与印刷“222”了。

  • 是因为我们采用的是dispatch_async,所以GCD会开发线程,同期鉴于是串行队列,所以GCD只开辟了一个线程来实践串行队列里的5个职务。

  • 因为咱们应用的是串行队列,所以队列里的职责在子线程中是按梯次实践的。

唯独请留心:串行队列里的任务肯定是按顺序施行的呢?

不是,那要看您创立了多少个串行队列了,多少个串行队列里的天职一定是按梯次施行的,可是四个串行队列之间是能够并行实施的。比如大家那边创办5个串行队列,二个行列里二个职分,那GCD就能开垦5个线程,那样5个串行队列里的任务便是并行实践。

只是这种做法是值得提道的,因为大气的创建线程会促成不小的内部存款和储蓄器开支,所以在利用串行队列的时候,大家应该只创设绝对有不能缺少的串行队列,而不能够想成立多少就创办多少。

5.dispatch_semaphore_signal

// dispatch_semaphore_signal有两类用法:a、解决同步问题;b、解决有限资源访问(资源为1,即互斥)问题。
// dispatch_semaphore_wait,若semaphore计数为0则等待,大于0则使其减1。
// dispatch_semaphore_signal使semaphore计数加1。

// a、同步问题:输出肯定为1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    // 任务1
    dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
    NSLog(@"1n");
    dispatch_semaphore_signal(semaphore2);
    dispatch_semaphore_signal(semaphore1);
});

dispatch_async(queue, ^{
    // 任务2
    dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
    NSLog(@"2n");
    dispatch_semaphore_signal(semaphore3);
    dispatch_semaphore_signal(semaphore2);
});

dispatch_async(queue, ^{
    // 任务3
    dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
    NSLog(@"3n");
    dispatch_semaphore_signal(semaphore3);
});

// b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
    // 任务
    ...
    dispatch_semaphore_signal(semaphore);
    });
}
2、dispatch_async + 并行队列

我们把5个任务异步地分配到1个相互队列里:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; NSLog;}

输出为:

111222<NSThread: 0x600002708180>{number = 6, name = }<NSThread: 0x600002708080>{number = 4, name = }<NSThread: 0x600002708140>{number = 5, name = }<NSThread: 0x600002707c80>{number = 3, name = }3421<NSThread: 0x600002708140>{number = 5, name = }5

浅析一下:

  • 大家看来此间有着的职分都以使用dispatch_async来分配的,而dispatch_async不会堵塞调用它的线程,所以打字与印刷完“111”后就一直打字与印刷“222”了。

  • 出于大家利用的是dispatch_async,所以GCD会开拓线程,同期鉴于是互相队列,所以GCD开拓了多少个线程来实施队列里的天职。

  • 因为我们运用的是相互队列,所以队列里的任务在多少个子线程中的推行是冬日的。

6.dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f

// dispatch_set_context、dispatch_get_context是为了向队列中传递上下文context服务的。
// dispatch_set_finalizer_f相当于dispatch_object_t的析构函数。
// 因为context的数据不是foundation对象,所以arc不会自动回收,一般在dispatch_set_finalizer_f中手动回收,所以一般讲上述三个方法绑定使用。

- (void)test
{
    // 几种创建context的方式
    // a、用C语言的malloc创建context数据。
    // b、用C++的new创建类对象。
    // c、用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    if (queue) {
        // "123"即为传入的context
        dispatch_set_context(queue, "123");
        dispatch_set_finalizer_f(queue, &xigou);
    }
    dispatch_async(queue, ^{
        char *string = dispatch_get_context(queue);
        NSLog(@"%s", string);
    });
}

// 该函数会在dispatch_object_t销毁时调用。
void xigou(void *context)
{
    // 释放context的内存(对应上述abc)

    // a、CFRelease(context);
    // b、free(context);
    // c、delete context;
}
3、dispatch_sync + 串行队列

我们把5个任务同步地分配到1个串行队列里:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; NSLog;}

输出:

111<NSThread: 0x6000032dc9c0>{number = 1, name = main}1<NSThread: 0x6000032dc9c0>{number = 1, name = main}2<NSThread: 0x6000032dc9c0>{number = 1, name = main}3<NSThread: 0x6000032dc9c0>{number = 1, name = main}4<NSThread: 0x6000032dc9c0>{number = 1, name = main}5222

深入分析一下:

  • 笔者们见到这里具有的天职都以行使dispatch_sync来分配的,而dispatch_sync会阻塞调用它的线程,所以打印完“111”后,会实施它分配的第叁个职责,实践完第二个职责后又会实行它分配的第一个职责,依次类推,直到dispatch_sync分配的持有职务都实践完了,线程阻塞甘休,才打印“222”。

  • 鉴于大家应用的是dispatch_sync,所以GCD不会开拓线程,而是把义务都得到调用dispatch_sync函数的线程中去施行。

  • 大家来看只假设应用dispatch_sync,是串行队列,职责是按顺序试行的。

死锁

4、dispatch_sync + 并行队列

我们把5个任务同步地分配到1个互相之间队列里:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_sync(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; NSLog;}

输出:

111<NSThread: 0x6000032dc9c0>{number = 1, name = main}1<NSThread: 0x6000032dc9c0>{number = 1, name = main}2<NSThread: 0x6000032dc9c0>{number = 1, name = main}3<NSThread: 0x6000032dc9c0>{number = 1, name = main}4<NSThread: 0x6000032dc9c0>{number = 1, name = main}5222

浅析一下:

  • 同3。

  • 大家看看只假设选择dispatch_sync,这里纵然是互相队列,职务也是按梯次实行的。

其实上边三种状态,也只是一种情景了,因为主队列也是八个串行队列。所以大家可以总括一句话在一个串行队列里,dispatch_sync一个任务到该串行队列,就会造成死锁

dispatch_sync

// 假设这段代码执行于主队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 在主队列添加同步任务
dispatch_sync(mainQueue, ^{
    // 任务
    ...
});

// 在串行队列添加同步任务 
dispatch_sync(serialQueue, ^{
    // 任务
    ...
    dispatch_sync(serialQueue, ^{
        // 任务
        ...
    });
};
1、死锁意况一:在主线程中,dispatch_sync二个任务到主队列
- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_main_queue(); NSLog; dispatch_sync(queue, ^{ NSLog; }); NSLog;}

解析一下:

职责加多阶段:程序在运维后,系统会自动把主线程里要举办的天职都丰盛到主队列。那么这里,系统会把职责1和任务3先增加到主队列里,然后再把职分2扩大到职务3后头。

职务推行阶段:职分在试行的时候,实行完职务1,dispatch_sync会阻塞主线程来举办职分2,可是大家地点说了义务2是被追加到职责3后头的,而队列又不能够不是先进先出,所以职责2想进行就得等职分3推行完,而那边职责3想要执行就亟须得等职务2实行完,就导致了死锁。

dispatch_apply

// 因为dispatch_apply会卡住当前线程,内部的dispatch_apply会等待外部,外部的等待内部,所以死锁。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任务
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任务
        ...
    });
});
2、死锁情形二:在二个串行队列里,dispatch_sync叁个任务到该串行队列
- viewDidLoad { [super viewDidLoad]; dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); NSLog; dispatch_async(serialQueue, ^{ NSLog; dispatch_sync(serialQueue, ^{ NSLog; }); NSLog; }); NSLog;}

分析一下:

任务增添阶段:程序运维后,主队列内任务有1、5,自定义串行队列里有职分2、4、3。

职务实践阶段:职分在实践的时候,实行完职责1,dispatch_async不会卡住主线程,所以会试行任务5,然后子线程中实践义务2,而dispatch_sync又会卡住子线程来实施职责3,可是义务3是被追加到职务4前边的,而队列又不可能不是先进先出,所以任务3想举行就得等职分4举办完,而这里职分4想要试行就务须得等职分3施行完,就形成了死锁。

dispatch_barrier

dispatch_barrier_sync在串行队列和大局并行队列之中和dispatch_sync一样的效劳,所以需思考同dispatch_sync一样的死锁难题。

1、dispatch_once

dispatch_once用来保管一段代码在全方位程序的生命周期内只举办叁次,因此它是相对线程安全的,创立单例的时候我们会用到它。

static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ // 任务...});

别的令人吸引的标题

2、dispatch_after

dispatch_after用来延时多久后实行有个别职务。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 任务...});

唯独大家须求小心,这里实在并非可信赖地在3s后实行某些义务,而是在延时钦赐的时日后把义务增添到钦定的队列中,比方增多到主队列时,那个职分最快3s后执行,最慢s后实行。

队列和线程的关系

不当领会:

几个人会产生一种错觉,感觉队列就是线程。又微微人会有其余一种错觉,五个追加Block就是多个线程。

正确通晓:

对我们使用者来讲,与其说GCD是面向线程的,比不上说是面向队列的。 它遮掩了内部线程的调整。

我们所做的独自是创办不一致的体系,把Block追加到行列中去执行,而队列是FIFO(先进先出)的。
它会规行矩步大家扩展的Block的依次,在综合大家调用的gcd的api(sync、async、dispatch_barrier_async等等),以及依据系统负荷来增减线程并发数, 来调治线程实施Block。

小结一下:
往主队列提交Block,无论是sync,还是async,都以在主线程中执行。
往非主队列中提交,借使是sync,会在此时此刻交给Block的线程中实施。假诺是async,则会在分线程中执行。

3、dispatch_group

当队列中负有的职分推行完成后,想进行某些甘休操作时,能够运用dispatch_group,这种景观大家日常会遇到。

以此行列当然可以是串行队列也得以是相互队列,只可是假诺是串行队列时,大家能够把这么些结束操作作为最后贰个任务增多进去,不用dispatch_group也可以。例子如下:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_async(queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog(@"任务全部完成"); });}

输出为:

<NSThread: 0x600003b7c340>{number = 3, name = }1<NSThread: 0x600003b7c340>{number = 3, name = }2<NSThread: 0x600003b7c340>{number = 3, name = }3<NSThread: 0x600003b7c340>{number = 3, name = }任务全部完成

而并行队列就不能了,假使我们想要在并行实践的七个职分试行完结后推行有些操作就务须用dispatch_group,因为并行施行的职务我们不精通它们的进行顺序,不精晓究竟哪一个职务才是终极奉行完的那个。例子如下:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_global_queue; dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_group_async(group, queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_group_async(group, queue, ^{ NSLog(@"%@", [NSThread currentThread]); NSLog; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"任务全部完成"); });}

输出为:

<NSThread: 0x6000005186c0>{number = 4, name = }<NSThread: 0x6000005175c0>{number = 3, name = }<NSThread: 0x600000517680>{number = 5, name = }213任务全部完成

GCD的死锁

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"任务一");
});
NSLog(@"任务二");

如上,在主线程中,往主队列同步交付了任务一。因为往queue中提交Block,总是追加在队列尾巴部分的,而queue实践Block的一一为先进先出(FIFO),所以义务一供给在此时此刻队列实施完它前边全部的职分(举个例子职务二),技巧轮到它被实行。(注意,这里引起死锁并非因为义务二,哪怕删去职责二,这里依然会死锁。这里只是为着举个例子表达,看许三人都在费解那或多或少,特此表明...)
而职责二因为职分一的sync,被封堵了,它供给等职责一推行完才具被实践。两个并行等待对方实践完,手艺实行,程序被死锁在那了。
那边需求留意这里死锁的很要紧一个尺度也因为主队列是一个串行的队列(主队列中独有一条主线程)。如若大家如下例,在交互队列中提交,则不会招致死锁:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
  dispatch_sync(dispatch_get_global_queue(0, 0), ^{
      NSLog(@"任务一");
  });
  NSLog(@"任务二");
});

由来是相互队列中职务一虽被提交依然是在queue的队尾,在职务二后头,不过因为是互相的,所以职分一并不会从来等任务二得了才去实践,而是直接施行完。此时职务二的因为职务一的利落,sync阻塞也就打消,职责二得以实行。

上述第三个死锁的例子,大家相当粗略的改写一下,死锁就被免除了:

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任务一");
});
NSLog(@"任务二");

咱俩在主线程中,往全局队列同步交付了Block,因为全局队列和主队列是多少个体系,所以职责一的实践,并不供给等待任务二。所以等职务一截至,职分二也足以被实行。
理当如此这里因为提交Block所在队列,Block被试行的队列是截然两样的多少个类别,所以这边用串行queue,也是不会死锁的。

sync的堵塞机制:
sync提交Block,首先是阻塞的近些日子提交Block的线程(老妪能解下就是阻塞sync之后的代码)。例如大家之前举的例证中,sync总是阻塞了职责二的进行。
而在队列中,轮到sync提交的Block,仅仅阻塞串行queue,而不会卡住并行queue。(dispatch_barrier_(a)sync除却,我们后面会讲到。)

咱俩通晓了sync的堵塞机制,再组成产生死锁的根本原因来自于互相等待,我们用上面一句话来总计一下,会唤起GCD死锁的行为:
若是同步(sync)提交三个Block到多个串行队列,而付出Block那一个动作所处的线程,也是在脚下队列,就能挑起死锁。

4、dispatch_barrier_async

dispatch_barrier_async,正如它的名字barrier一样,是个栅栏,它会把在它前面分配和在它今后分配到行列里的职分给分隔断,那么在它前边的分配的职务总体实行实现在此之前,它之后分配的天职是纯属不会实行的。任务实施的逐个为:在它此前分配的任务总体进行实现,实施dispatch_barrier_async的天职,然后再奉行在它现在分配的义务,进而大家能够用它来成功高效的数据库访问,我们领略有关数据库的数额竞争难题,两个读操作竞争是不会生出多少难题的,而七个写操作竞争才会时有产生多少难点,由此大家常见的做法是把多少个读操作放在并行队列里。

今昔举例来看下,借使我们有个需如若读操作1、2、3能够随意地读,可是读操作4、5、6在读从前,必需试行三个写操作,并且还要确认保障读操作4、5、6读取的多少是新写进去的多寡。那我们着想到读操作数据竞争不会招致数据难点,所以会把那6个读操作放进一个相互队列里来提升数据读取的效能,那这些写操作该如何是好呢?怎么能力到达供给里的成效啊?用dispatch_barrier_async就足以了。如下:

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog; }); dispatch_async(queue, ^{ NSLog; }); dispatch_async(queue, ^{ NSLog; }); dispatch_barrier_async(queue, ^{ NSLog; }); dispatch_async(queue, ^{ NSLog; }); dispatch_async(queue, ^{ NSLog; }); dispatch_async(queue, ^{ NSLog; });}

输出:

读操作2读操作1读操作3写操作读操作6读操作4读操作5

分析一下:

大家得以看出队列里本来有6个读职务,它们是并行推行的,然而当在中途插入了一个barrier任务之后,它就如栅栏同样把职务1、2、3和4、5、6给隔离了,在职分1、2、3施行达成从前,职责4、5、6是纯属不只怕实行的,独有等职分1、2、3完了,並且barrier任务实践了,职务4、5、6才会重整旗鼓它自然的动静并行实施。

以下4个GCD方法的分别

dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
dispatch_barrier_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
5、dispatch_apply

dispatch_apply函数用来按钦命的次数把钦命的职分分配到行列中,它和dispatch_sync同样会堵塞当前线程。

- viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(11, queue, ^(size_t index) { NSLog(@"%ld", index); }); NSLog(@"全部执行结束");}

输出:

104756981032全部执行完毕

1. dispatch_barrier_async

它的职能能够用一个词回顾--承先启后,它保险之前的职务都早早本人试行,此后的天职也迟于本人推行。当然它的功力导致它独有在竞相队列中有意义。

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 任务1
    ...
});
dispatch_async(queue, ^{
    // 任务2
    ...
});
dispatch_async(queue, ^{
    // 任务3
    ...
});
dispatch_barrier_async(queue, ^{
    // 任务4
    ...
});
dispatch_async(queue, ^{
    // 任务5
    ...
});
dispatch_async(queue, ^{
    // 任务6
    ...
});

职分1,2,3的逐条不肯定,4在中游,最终是5,6职务逐个不必然。它就像二个栅栏一样,挡在了二个互相队列中间。
本来这里有好几亟待潜心的是:dispatch_barrier_(a)sync只在本身创办的并发队列上有效,在大局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

用途, 例如大家在二个读写操作中:
我们要掌握四个数目,读与读之间是能够用线程并行的,不过写与写、写与读之间,就亟须串行同步照旧使用线程锁来保管线程安全。可是我们有了dispatch_barrier_async,我们就能够如下使用:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    //读操作
});
dispatch_async(queue, ^{
    // 读操作
});
dispatch_barrier_async(queue, ^{
    // 写操作
});
dispatch_barrier_async(queue, ^{
    // 写操作
});
dispatch_async(queue, ^{
    // 读操作
});

2. dispatch_barrier_sync 和 dispatch_barrier_async 的区别

效果差不离同一,都足以在并行queue中充当栅栏。
dispatch_barrier_sync有GCD的sync共有天性,会阻塞提交Block的当下线程,而dispatch_barrier_async是异步提交,不会阻塞。

3. dispatch_sync 和 dispatch_barrier_sync 的区别

互相因为是sync提交,所以都是阻塞当前交给Block线程。
dispatch_sync并不能够围堵并行队列

dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
    dispatch_async(queue, ^{
        NSLog(@"任务二");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务三");
    });
    //睡眠2秒
    [NSThread sleepForTimeInterval:2];
    NSLog(@"任务一");
});

输出结果 :
任务三
任务二
任务一

很扎眼,并行队列未有被sync所不通。

而dispatch_barrier_sync能够隔离并行队列(栅栏作用的反映):

dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_sync(queue, ^{
    dispatch_async(queue, ^{
        NSLog(@"任务二");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务三");
    });
    //睡眠2秒
    [NSThread sleepForTimeInterval:2];
    NSLog(@"任务一");
});

输出结果 :
任务一
任务二
任务三
TAG标签:
版权声明:本文由990888藏宝阁发布于编程算法,转载请注明出处:其次篇:GCD