简介
GCD 全称为 Grand Central Dispatch,是 libdispatch 的市场名称,而 libdispatch 是 Apple 的一个库,其为并发代码在 iOS 和 macOS 的多核硬件上执行提供支持。确切地说 GCD 是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。这样通过 GCD 来管理线程,从而解决线程被创建的问题。
创建队列
dispatch_queue_create
主队列:一个特殊的串行队列,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。
|
|
手动创建队列:可以创建 串行队列, 也可以创建 并行队列。第一个参数是标识符,第二个参数用来表示创建的队列是串行的还是并行的。DISPATCH_QUEUE_SERIAL / NULL 串行队列;DISPATCH_QUEUE_CONCURRENT 并行队列。
|
|
创建任务
dispatch_async / dispatch_sync
同步派发(sync)会尽可能地在当前线程派发任务。但如果在其他队列往主队列同步派发,任务会在主线程执行;
异步派发(async)也不绝对会另开线程,例如在主线程异步派发到主线程,派发依旧是异步的,任务也会在主线程执行。
- dispatch_sync 同步任务:会阻塞当前线程;
- dispatch_async 异步任务:不会阻塞当前线程
|
|
dispatch_after
dispatch_after只是延时提交block,不是延时立刻执行。
|
|
dispatch_set_target_queue
dispatch_set_target_queue可以设置queue的优先级,也可以使多个serial queue在目标queue上一次只有一个执行。
(如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么多个串行queue在目标queue上就是同步执行的,不再是并行执行。
例如,把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这就可以使用dispatch_set_target_queue了。)
|
|
打印结果1、2、3。
dispatch_barrier_async / dispatch_barrier_sync
dispatch_barrier_async 这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
dispatch_barrier_sync 同上,除了它是同步返回函数。
打印结果:read 1、read 2、write 1、read 3、read 4。
dispatch_apply
dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回。
需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,所以这里会阻塞主线程,如需异步返回,使用dispatch_async包一下就不会阻塞了。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
|
|
对比:
|
|
Dispatch Block
dispatch_block_create
自己创建block并添加到queue中去执行。并且,在创建block时可以通过设置QoS,指定block对应的优先级,在dispatch_block_create_with_qos_class中指定QoS类别即可。
|
|
dispatch_block_wait
可以根据dispatch block来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束。
|
|
dispatch_block_notify
dispatch_block_notify当观察的某个block执行结束之后立刻通知提交另一特定的block到指定的queue中执行,该函数有三个参数,第一参数是需要观察的block,第二个参数是被通知block提交执行的queue,第三参数是当需要被通知执行的block,函数的原型。
|
|
dispatch_block_cancel
iOS8后GCD支持对dispatch block的取消
|
|
Dispatch_groups
当我们想在gcd queue中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用 dispatch_group 来实现了,dispatch_group 能很方便的解决同步的问题。dispatch_group_create可以创建一个group对象,然后可以添加block到该组里面。
dispatch_group_create
创建dispatch_group_t
|
|
dispatch_group_async
自己创建队列时,当然就用dispatch_group_async函数,简单有效。
|
|
dispatch_group_wait
dispatch_group_wait会同步地等待group中所有的block执行完毕后才继续执行,类似于dispatch barrier
|
|
dispatch_group_notify
功能与dispatch_group_wait类似,不过该过程是异步的,不会阻塞该线程,dispatch_group_notify有三个参数,第一个参数指定要观察的group,第二个参数指定block待执行的队列,第三个参数指定group中所有任务执行完毕之后要执行的block。
|
|
dispatch_group_enter / dispatch_group_leave
|
|
dispatch_semaphore_create
dispatch semaphore用来做解决一些同步的问题,dispatch_semaphore_create会创建一个信号量,该函数需要传递一个信号值,dispatch_semaphore_signal会使信号值加1,如果信号值的大小等于1,dispatch_semaphore_wait会使信号值减1,并继续往下走,如果信号值为0,则等待。
|
|
Dispatch Source
dispatch源(dispatch source)和RunLoop源概念上有些类似的地方,而且使用起来更简单。要很好地理解dispatch源,其实把它看成一种特别的生产消费模式。dispatch源好比生产的数据,当有新数据时,会自动在dispatch指定的队列(即消费队列)上运行相应地block,生产和消费同步是dispatch源会自动管理的。
Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表:
Methods | explain |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 变量增加 |
DISPATCH_SOURCE_TYPE_DATA_OR | 变量OR |
DISPATCH_SOURCE_TYPE_MACH_SEND | Mach端口发送 |
DISPATCH_SOURCE_TYPE_MACH_RECV | MACH端口接收 |
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | 内存压力 |
DISPATCH_SOURCE_TYPE_PROC | 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号 |
DISPATCH_SOURCE_TYPE_READ | IO操作,如对文件的操作、socket操作的读响应 |
DISPATCH_SOURCE_TYPE_SIGNAL | 接收到UNIX信号时响应 |
DISPATCH_SOURCE_TYPE_TIMER | 定时器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件状态监听,文件被删除、移动、重命名 |
DISPATCH_SOURCE_TYPE_WRITE | IO操作,如对文件的操作、socket操作的写响应 |
方法:
- dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
- dispatch_source_set_event_handler:设置事件处理handler
- dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。
|
|
dispatch_time_t
|
|
dispatch_source_set_timer
|
|
第一个参数:定时器对象;第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时;第三个参数:间隔时间 GCD里面的时间最小单位为 纳秒;第四个参数:精准度(表示允许的误差,0表示绝对精准)。
NSTimer在主线程的runloop里会在runloop切换其它模式时停止,这时就需要手动在子线程开启一个模式为NSRunLoopCommonModes的runloop,如果不想开启一个新的runloop可以用不跟runloop关联的dispatch source timer。
NSEC_PER_SEC 1000000000ull
USEC_PER_SEC 1000000ull
NSEC_PER_USEC 1000ull
NSEC:纳秒;USEC:微妙;SEC:秒;PER:每。
|
|
dispatch_suspend和dispatch_resume
- dispatch_suspend 挂起队列
- dispatch_resume 恢复队列
dispatch_suspend这里挂起不会暂停正在执行的block,只是能够暂停还没执行的block。
|
|
可知,在dispatch_suspend挂起队列后,第一个block还是在运行,并且正常输出。
结合文档,我们可以得知,dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
死锁!
dispatch_sync导致的死锁
在main线程使用“同步”方法提交Block,必定会死锁:
|
|
嵌套调用可能就会造成死锁:
|
|
其它情况:
|
|
dispatch_apply导致的死锁:
|
|