GCD的API
1. Dispatch Queue
Dispatch Queue分为两种
① DISPATCH_QUEUE_SERIAL 等待现在执行中处理结束
② DISPATCH_QUEUE_CONCURRENT 不等待现在执行中处理结束
如何得到Dispatch Queue?
方法一:通过GCD的API生成
- 生成 DISPATCH_QUEUE_SERIAL
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("gcd.MySerialDispatchQueue",
DISPATCH_QUEUE_SERIAL);
- 生成 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.png5. 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.png8.dispatch_suspend/dispatch_resume
dispatch_suspend函数挂起指定的Dispatch Queue;
dispatch_resume函数恢复指定的Dispatch Queue;
注:这些函数对已经执行的处理没有影响。挂起后追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使这些处理能够继续执行;
9.Dispatch Semaphore
信号量 Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,大于等于一时减去一而不等待;
image.pngdispatch_semaphore_wait函数等待计数值达到大于等于1
参数1:为信号量
参数2:由dispatch_time_t函数指定的等待时间;
该函数的返回值与dispatch_group_wait函数相同可通过返回值进行分支处理
注:
在没有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的种类
事件发生时,在指定的Dispatch Queue中可执行事件的处理。
注:
- Dispatch Queue没有“取消”这一概念。一旦将处理追加到Dispatch Queue中,就没有办法将该处理去除,也没有办法可以在执行中取消处理。编程人员要么在处理中导入取消这一概念,要么放弃取消,或者使用NSOperationQueue等其他方法
- Dispatch Source 与Dispatch Queue不同,是可以取消的。而且取消时必须执行的处理可指定为回调用的Block形式。因此使用Dispatch Source 实现XNU内核中发生的事件处理要比直接使用kqueue实现更为简单。
网友评论