iOS多线程 之 GCD 四个心法 七个招式

左手画方 右手画圆

“少侠,你能左手画方,又手画圆吗?”

“这有何难,开始看我表演吧”

左方右圆

“看,怎么样,厉害吧”

“鹅 .... 厉害...”

“少侠,其实我是说你在iOS代码里能左手画方,右手画圆吗?”

“原来是这个意思,就是多线程嘛,这有何难,看我用GCD展示一下”

NSLog(@"左手画方");  

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
      NSLog(@"右手画圆");
});

//输出
左手画方
右手画圆

“怎么样厉害吧”

“少侠确实有两下子,那对于GCD,少侠还有其他用法嘛”

“啥?其他用法,不需要的,只需要如上我这等操作,就能解决大部分问题,其他用法,不需要的。”

“非也、非也,GCD这本秘籍还是有很多招式的,且听老夫给你细细道来。”

GCD的四个基本心法

同步 画-完-告

小师妹让你画一个方,收到任务你开始画方,画完之后告知小师妹,方已完成。同步要阻塞当前线程,必须要等待当前任务执行完,返回以后,才会继续执行下一个任务。

异步 画-立-告

小师妹让你画一个方,收到任务后,为了显示你的厉害所在,你立马告知小师妹,方我已经着人去画啦,你且等着吧,很快就有消息了。异步不会阻塞当前线程,会立即返回,可立刻执行下一个任务。

串行队列 一心一用

小师妹让你画一方一圆,收到任务,你的全部注意力放在一个地方,左手画方的时候,全部注意力在左手,所以此时右手是不动的,等待左手画完方之后,全部注意力集中右手,这时右手再画圆。此为先左手画方,后右手画圆。一心一用理解为串行,串行队列中的任务只能一次执行一个。

串行队列

并行队列 一心两用

你的注意力分开两部分,一部分在左手,一部分在右手,然后左手画方,之后立马开始右手画圆,一心两用理解为并行。并行队列中的任务会按照加入队列中的顺序执行,但哪个任务开始执行完是不定的。

并行队列
GCD的七个基本招式

“等等,大师,你的这些心法,貌似没什么用啊,我的招式还是能解决大部门问题啊”

“那老夫就来问几个小问题,看招”

“多个线程同时读写一份数据,如何做到不出错?
多个异步任务都完成之后,才能告知小师妹,如何做到?

“鹅....”

“嘿嘿,不懂了吧,下面老夫就来给你展示GCD的八个基本招式,学会基本招式,这些问题就迎刃而解了”

dispatch_sync

NSLog(@"画方");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       NSLog(@"画圆");
});
NSLog(@"吹口哨")

//输出
画方
画圆
吹口哨

dispatch_sync 会阻塞当前线程,等待block中的任务执行完毕后,才会执行下一个任务,所以执行顺序是 画方-画圆-吹口哨。

dispatch_async

NSLog(@"画方");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       NSLog(@"画圆");
});
NSLog(@"吹口哨")

//输出
画方
吹口哨
画圆

dispatch_async 不会阻塞当前线程,会立即执行下一个任务。所以执行顺序是 画方-吹口哨-画圆。

dispatch_after

指定时间后,将要执行的任务添加到指定的队列中去。

//两秒之后吹口哨
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSLog(@"吹口哨");
});

示例中2秒钟之后,将任务添加到主队列中,如果主队列中这时有其他的耗时操作,那么将会等待主队列中的任务执行完以后,才会执行添加的任务。 所以dispatch_after是不准确的。

dispatch_once

+ (instancetype)shareUserInfo
{
    static UserInfo *shareUserInfo = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareUserInfo = [[UserInfo alloc] init];
    });
    return shareUserInfo;
}

dispatch_once可以帮助我们创建单例,再也不用像以前一样费心费力啦。

dispatch_barrier

在使用dispatch_barrier的队列中,执行顺序如下:

  • 先于dispatch_barrier之前加入队列的任务先执行。
  • dispatch_barrier 中的任务执行。
  • 晚于dispatch_barrier添加到队列的任务最后执行。
//不使用dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"画方");
    });
    
dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"画圆");
});
    
NSLog(@"吹口哨");
    
dispatch_async(queue, ^{
    NSLog(@"撩师妹");
});
dispatch_async(queue, ^{
    NSLog(@"告师傅");
}); 

//输出
吹口哨
撩师妹
告师傅
画方
画圆  

因为画方画圆的时间太久,导致了活儿还没干,就先吹口哨了,这是不对的。使用dispatch_barrier再试一次。

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    sleep(2);
    NSLog(@"画方");
});
    
dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"画圆");
});

dispatch_barrier_sync(queue, ^{
    sleep(4);
    NSLog(@"吹口哨");
});
    
dispatch_async(queue, ^{
    NSLog(@"撩师妹");
});
dispatch_async(queue, ^{
    NSLog(@"告师傅");
}); 

//输出
画方
画圆
吹口哨
撩师妹
告师傅

可以看到虽然画方画圆的时间比较久,吹口哨因为技术不成熟,也耗时较久,但最终是按照正常流程进行,活儿没干完坚决不撩师妹。

撩师妹

清楚了吧,吹口哨的时候,其他的都靠边站,就是这么横。

“大师,为什么图中是并行队列,而不是串行队列?”

“因为串行队列本来就是按顺序执行的...”

dispatch_group

画方-画圆-吹口哨-撩师妹-告师傅,流程行云流水。 现在新的问题来了,那想在画方-画圆之后,喝口水再吹口哨要怎么做呢?

首先来分析一下问题:

  1. 画方画圆属于耗时的任务,现在把它们放在了异步线程。异步线程的执行时间,是不知道的。
  2. 两个异步线程任务都执行完之后,喝水。

要达到上面的要求,一个比较普通的流程是画方完成后用一个变量记录,画圆完成后用另外一个变量记录。当两个变量都记录到完成之后,就可以继续下一步了,喝水。

麻烦吗? 也不麻烦,才两个变量而已。但是上面的耗时操作如果是5个呢? 麻烦! dispatch_group 可以方便的解决这个问题。

dispatch_group_t group = dispatch_group_create();
    
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    sleep(3);
    NSLog(@"画方完成");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    sleep(2);
    NSLog(@"画圆完成");
    dispatch_group_leave(group);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"都完成了 喝口水");
});

//输出 
画圆完成
画方完成
都完成了 喝口水

少侠你看,前面提到的问题之一:“多个异步任务都完成之后,才能告知小师妹,如何做到?” 就这样解决了。

有一点要注意,dispatch__group__enterdispatch__group__leave必须成对出现,不然会崩溃的。

dispatch_semaphore

信号量可以控制多个线程对有限资源的访问。

dispatch_semaphore只有三个方法:

//创建信号量
dispatch_semaphore_create
//发送信号量
dispatch_semaphore_signal
//等待信号量
dispatch_semaphore_wait

以少侠为例:

少侠现在要去马场,给你的汗血宝马喂草,但是马场的马槽是有限的,其他的人也要喂马,少侠纵然有绝世武功,但人在江湖,依然要讲究江湖规矩——排队。

dispatch__semaphore__create 创建时,传入的数值,为马槽数量。

dispatch__semaphore__signal 相当于走了一匹马。

dispatch__semaphore__wait 相当于来了一匹马。

调用一次 dispatch__semaphore__signal 就走了一匹马,调用一次 dispatch__semaphore__wait 就来了一匹马,当马槽的数量为0时,再来的马就只能等待了。如果有耐心可以一直等,没有耐心,等半个时辰也可以走。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备画方");
    sleep(2);
    NSLog(@"画方");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备画圆");
    sleep(2);
    NSLog(@"画圆");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备吹口哨");
    sleep(2);
    NSLog(@"吹口哨");
    dispatch_semaphore_signal(semaphore);
});

//输出

准备吹口哨
准备画方
准备画圆
画圆
吹口哨
画方

创建信号量值为3,代表最多可以三处资源同时访问,当前有3个线程,所以当前没有资源控制,可以到看三个任务同时准备同时完成。

下面把信号量的值改为2

dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备画方");
    sleep(2);
    NSLog(@"画方");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备画圆");
    sleep(2);
    NSLog(@"画圆");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"准备吹口哨");
    sleep(2);
    NSLog(@"吹口哨");
    dispatch_semaphore_signal(semaphore);
});

//输出
准备画圆
准备画方
画圆
画方
准备吹口哨
吹口哨

创建信号量值为2,代表最多可以两处资源同时访问。但当前有三个线程,所以前面两个任务 画圆、画方会先完成,之后再吹口哨。

勤加练习

少侠,到目前为止,老夫已经给你讲了GCD的四个基本心法,七个基本招式,这些都只是基本功,少侠日后必当好好练习,才能像更高的层次冲击。老夫累了,这就闭关了,他日你练成神功,老夫定当出关,助你再占高峰。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 500评论 0 1
  • 机遇并不是随处可见,努力是生活必备 前面几篇关于暑假工的事情讲得有些冗长,感觉自己会说得虎头蛇尾。所以我想那些...
    六六和七七阅读 263评论 0 0
  • 五年级了,我到了新教室,遇到了新同学,还意外的认识了一位新朋友,这位朋友住在教室窗外,身披灰色大衣,头戴黑帽...
    钟承效阅读 467评论 0 2