iOS多线程GCD

GCD的API

1. Dispatch Queue

Dispatch Queue分为两种

① DISPATCH_QUEUE_SERIAL  等待现在执行中处理结束
② DISPATCH_QUEUE_CONCURRENT  不等待现在执行中处理结束

如何得到Dispatch Queue?

方法一:通过GCD的API生成

  1. 生成 DISPATCH_QUEUE_SERIAL
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("gcd.MySerialDispatchQueue", 
DISPATCH_QUEUE_SERIAL);
  1. 生成 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

方法二:获取系统提供的标准Dispatch Queue

名称 Dispatch Queue种类 说明
Main Dispatch Queue SerialDispatchQueue 主线程执行
Global Dispatch Queue(High priority) ConcurrentDispatchQueue 执行优先级:高
Global Dispatch Queue(Default) ConcurrentDispatchQueue 执行优先级:默认
Global Dispatch Queue(Low) ConcurrentDispatchQueue 执行优先级:低
Global Dispatch Queue(Background) ConcurrentDispatchQueue 执行优先级:后台
  /*
     * Main Dispatch Queue的获取方法
     */
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    /*
     * Global Dispatch Queue(高优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    /*
     * Global Dispatch Queue(默认优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /*
     * Global Dispatch Queue(低优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    /*
     * Global Dispatch Queue(后台优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
image.png

2. dispatch_set_target_queue(设定Dispatch Queue优先级)

作用: dispatch_queue_creat函数生成的Dispatch Queue 不管是 Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用默认优先级Global Dispatch Queue相同执行优先级的线程。此函数用来变更由dispatch_queue_creat函数生成的Dispatch Queue的优先级

    //mySerialDispatchQueue为默认优先级经过dispatch_set_target_queue函数将其设置为后台优先级
    
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("gcd.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL
                                                                   );
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

dispatch_set_target_queue将第一个参数的优先级变为第二个参数的优先级

3.dispatch_after(延迟处理)

dispatch_after 并不是在指定时间后执行处理,只是在指定时间追加处理到Dispatch Queue。

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)

参数一: 指定时间用的dispatch_time_t类型的值。该值使用dispatch_time函数或dispatch_walltime 函数得到。
参数二:指定要追加处理的Dispatch Queue
参数三:指定记述要执行处理的Block

4. Dispatch Group(调度组)

作用:组中处理顺序任意,但结束处理一定是在组中所有的处理都执行结束后才执行

image.png

5. dispatch_barrier_async(异步调度屏障)

该函数同dispatch_queue_creat函数生成的Concurrent Dispatch Queue一起使用

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束后再将指定的处理追加到该Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函数追加的执行处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作

image.png

注:可以用Dispatch Group和dispatch_set_target_queue来实现,但代码复杂
使用 dispatch_barrier_async和Concurrent Dispatch Queue函数可实现高效率的数据库访问和文件访问。

6.dispatch_sync(同步调度)

  • dispatch_async函数的“async”意味着“非同步”(asynchronous),就是将指定的block“非同步”地追加到指定的Dispatch Queue中,diispatch_async函数不做任何等待。

  • dispatch_sync函数的“sync”意味着“同步”(synchronous),就是将指定的block“同步”地追加到指定的Dispatch Queue中。再追加block结束之前dispatch_sync函数会一直等待

发生死锁的情况

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@“hello");});

该源代码在Main Dispatch Queue 中执行Block,并等待其执行结束。而其实在主线程中正在执行这些代码,所以无法执行追加到Main Dispatch Queue 的Block。

下面的例子也一样。

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{NSLog(@“hello");});

Main Dispatch Queue 中执行的Block 等待Main Dispatch Queue 中要执行的Block执行结束,造成死锁。

Serial Dispatch Queue也会引起同样的问题

 dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("gcd.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);

 dispatch_async(mySerialDispatchQueue, ^{ NSLog(@"hello"); });

7.dispatch_apply

作用: 提交一个Block到一个分发队列,以供多次调用。dispatch_apply函数是dispatch_sync函数和Dispatch Queue的关联API。该函数按指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

image.png

因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定。淡水输出结果最终的done必定在最后位置上,因为dispatch_apply函数会等待全部处理执行结束。

参数说明:

参数一:Block的重复次数
参数二:追加对象的Dispatch Queue
参数三:带有参数的Block,为了按照第一个参数重复追加Block并区分各个Block而使用

由于dispatch_apply函数与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数

image.png

8.dispatch_suspend/dispatch_resume

dispatch_suspend函数挂起指定的Dispatch Queue;
dispatch_resume函数恢复指定的Dispatch Queue;

注:这些函数对已经执行的处理没有影响。挂起后追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使这些处理能够继续执行;

9.Dispatch Semaphore

信号量 Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,大于等于一时减去一而不等待;

image.png

dispatch_semaphore_wait函数等待计数值达到大于等于1

参数1:为信号量
参数2:由dispatch_time_t函数指定的等待时间;
该函数的返回值与dispatch_group_wait函数相同可通过返回值进行分支处理

image.png

注:
在没有Serial Dispatch Queue 和dispatch_barrier_async函数那么大粒度且一部分需要进行排他控制的情况下,Dispatch Semaphore便可发挥威力

10.dispatch_once

dispatch_once函数是保证在应用程序中只执行一次指定处理的API。
通常在单例模式中用到


image.png

11.Dispatch I/O

作用: 使用多个线程更快的并列读取

  • dispatch_io_creat函数用来生成Dispatch I/O,并指定发生错误时使用的Block
  • dispatch_io_set_low_water设定一次读取的大小(分割大小)
  • dispatch_io_read函数使用Global Dispatch Queue 开始并列读取。每当各个分割的文件块读取结束时,将含有文件款数据的 DispatchData传递给dispatch_io_read函数指定的读取结束时回调用的Block。回调用的Block分析传递过来的Dispatch Data 并进行结合处理
    注:如果想提高文件读取速度,可以尝试使用Dispatch I/O

二、GCD的实现

1.要点:

1. IOS和OS X的核心是XNU内核,GCD是基于XUN内核实现的

2 .GCD的API全部在libdispatch库中

3 .GCD的底层实现主要有Dispatch Queue 和Dispatch Source

Dispatch Queue :管理block操作
Dispatch Source :处理事件(比如线程间通信)

2.Dispatch Queue 实现

Dispatch Queue 对于我们开发者来说应该是非常熟悉了,运用的场景非常之多,但是他的内部是如何实现的呢?
  • 用于管理追加的Block的C语言层实现的FIFO队列
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用于管理线程的C语言层实现的一些容器

不难想象,GCD的实现需要使用以上这些工具,但是如果仅用这些内容便可实现,那么就不需要内核级实现了。(实际上在一般的Linux内核中可能使用面向Linux操作系统而移植的GCD)。

甚至有人会想,只要努力编写线程管理的代码,就根本用不到GCD,是这样的吗?

我们先来回顾一下苹果的官方说明:

通常,应用程序中编写的线程管理用的代码要在系统级实现。

实际上正如这句话所说,在系统级即iOS和OS X的核心XNU内核级上实现,因此无论编程人员如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

使用GCD要比使用pthreads和NSThread这些一般的多线程编程API更好。并且,如果用GCD就不必编写为操作线程反复出现的类似的源代码(这里被称为固定源代码片段),而可以在线程中集中实现处理内容,真的是好处多多。我们尽量多使用GCD或者使用了Cocoa框架GCD的NSOperationQueue类等API。

那么首先确认一下用于实现Dispatch Queue 而使用的软件组件。如表所示:

组件名称 提供技术
libdispatch Dispatch Queue
Libc(pthreads) pthread_workqueue
XNU内核 workqueue
  • 编程人员所使用GCD的API全部包含在libdispatch库的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列主要是负责管理通过dispatch_async等函数所追加的一系列Blocks。可以理解为一旦我们在程序中由上到下追加了一组Blocks,那么排除掉dispatch_after,其内部的追加过程是一个先进先出原则。
  • 但是Block本身并不是直接加入到这个FIFO队列中,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后再进入FIFO队列。该结构体用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文(execution context)。
  • Dispatch Queue可通过dipatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue,但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的Global Dispatch Queue。

Main Dispatch Queue 在RunLoop中执行Block。

Global Dispatch Queue有如下8种:
Global Dispatch Queue (High priority)
Global Dispatch Queue (Default priority)
Global Dispatch Queue (Low priority)
Global Dispatch Queue (Background priority)
Global Dispatch Queue (High overcommit priority)
Global Dispatch Queue (Default overcommit priority)
Global Dispatch Queue (Low overcommit priority)
Global Dispatch Queue (Background overcommit priority)

注意前面四种 和后面四种不同优先级的Queue有一词之差:Overcommit。

其区别就在于 Overcommit Queue不管系统状态如何都会强制生成线程队列。
这8种Global Dispatch Queue 各使用一个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_wrokqueue。
pthread_wrokqueue包含在Libc提供的pthreads API中。它通过系统的bsdthread_register和workq_open函数调用,在初始化XNU内核的workqueue之后获取其信息。

XNU内核有4种workqueue:

WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE

以上为4种执行优先级的workqueue。该执行优先级与Global Dispatch Queue的4种执行优先级相同。

Dispatch Queue中执行Block的过程:


image.png

① 当在Global Dispatch Queue 中执行Block时,libdispatch 从Global Dispatch Queue自身的FIFO队列取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue 本身、符合其优先级的workqueue信息以及执行Dispatch Continuation的回调函数等传递给参数。

② pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的Global Dispatch Queue ,workqueue则始终生成线程。

注:该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有一部分pthread API不能使用。详细信息可以参考苹果的官方文档《并发编程指南》的“Compatibility with POSIX Threads“这一章节。
另外,因为workqueue生成的线程在实现用于workqueue的线程计划表中运行,他的上下文切换(shift context)与普通的线程有很大的不同。这也是我们使用GCD的原因。

③ workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数。在该回调函数中执行加入到Global Dispatch Queue中的下一个Block。
以上就是Dispatch Queue执行的大概过程。
由此可知,在编程人员自己编写的线程管理代码中想发挥出原生GCD的性能是不可能的。

3.Dipatch Source

GCD中除了主要的Dispatch Queue以外,还有不太引人注目的Dispatch Source。它是一种BSD系列内核惯有功能kqueue的封装。
kqueue是在XNU内核中由发生各种事件时,在应用程序层面来处理的技术。其CPU符合非常小,基本上不占用资源。kqueue可以说是应用程序处理XNU内核中的事件处理机制中最为优秀的。
Dispatch Source 可以处理以下事件。如表所示:
Dispatch Source的种类

image.png

事件发生时,在指定的Dispatch Queue中可执行事件的处理。

注:

  • Dispatch Queue没有“取消”这一概念。一旦将处理追加到Dispatch Queue中,就没有办法将该处理去除,也没有办法可以在执行中取消处理。编程人员要么在处理中导入取消这一概念,要么放弃取消,或者使用NSOperationQueue等其他方法
  • Dispatch Source 与Dispatch Queue不同,是可以取消的。而且取消时必须执行的处理可指定为回调用的Block形式。因此使用Dispatch Source 实现XNU内核中发生的事件处理要比直接使用kqueue实现更为简单。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容