什么是GCD
Grand Central Dispatch(GCD)
是异步执任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发只需要定义想执行并追加到适当的Dispatch Queue
中,GCD
就能生成必要的线程并计划执行任务,由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可以执行任务,这样就比以前的线程更有效率。
关于多线程编程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流。
一个CPU
一次只能执行一个命令,不能执行某处分开的并列的两个命令。因此通过CPU
执行的CPU命令
就好比一条无分叉的大道,其执行不会出现分歧。
OS X
和iOS
的核心XNU内核
在发生操作系统事件时,会切换执行路径,执行中的状态,例如CPU
的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU
寄存器等信息,继续执行切换路径的CPU
命令行。这就称为上下文切换
。
由于使用多线程的程序可以在某个线程和其他线程之间反复多次的进行上下文切换,因此看上去就好像1个CPU
核能够并列的执行多个线程一样,而且在具有多个CPU
核的情况下,就是真的提供多个CPU
核并行执行多个线程的技术。
这种利用多线程编程的技术就称为“多线程编程”。
但是,多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。
GCD 的 API
Dispatch Queue
dispatch_async(dispatch_queue_t _Nonnull queue, ^{
// doSomething
});
Dispatch Queue
, 是执行处理的等待队列。
使用Block语法定义想执行的任务
,通过dispatch_async
函数追加赋值在变量queue的Dispatch Queue
中。仅这样就可以使指定的Block
在另外一个线程执行。
Dispatch Queue
按照FIFO(First-In-First-Out)执行处理。
Dispatch Queue
有两种:
Dispatch Queue | 说明 |
---|---|
Serial Dispatch Queue | 等待现在执行中处理结果 |
Concurrent Dispatch Queue | 不等待现在执行中处理结果 |
dispatch_queue_create
通过dispatch_queue_create
生成Dispatch Queue。
如下生成一个 Serial Dispatch Queue:
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr);
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.mySerialDispatchQueue", NULL);
第一个参数指定Dispatch Queue
的名称,第二个参数指定NULL/DISPATCH_QUEUE_SERIAL
生成Serial Dispatch Queue
。。生成Concurrent Dispatch Queue
时,将第二个参数指定为DISPATCH_QUEUE_CONCURRENT
,如下:
dispatch_queue_t myConcurretnDispatchQueue = dispatch_queue_create("com.example.gcd.myConCurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create 函数的返回值为表示Dispatch Queuede的 dispatch_queue_t类型。
Serial Dispatch Queue
一次只能执行一个追加任务。系统对一个Serial Dispatch Queue
只生成一并使用一个线程,如果生成2000个Serial Dispatch Queue
,那么就生成2000个线程。如果过多使用多线程就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。
只在为了避免多线程编程问题之一——多个线程更新相同资源导致数据竞争时,使用Serial Dispatch Queue
下面分别添加几个block到两个队列中: Serial Dispatch Queue
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.mySerialDispatchQueue", NULL);
dispatch_async(mySerialDispatchQueue, ^{ NSLog(@"blcok1"); });
dispatch_async(mySerialDispatchQueue, ^{ NSLog(@"block2"); });
dispatch_async(mySerialDispatchQueue, ^{ NSLog(@"block3"); });
dispatch_async(mySerialDispatchQueue, ^{ NSLog(@"block4"); });
...
输出:
2017-01-19 20:23:50.599 gcd[13765:2856357] blcok1
2017-01-19 20:23:50.599 gcd[13765:2856357] block2
2017-01-19 20:23:50.599 gcd[13765:2856357] block3
2017-01-19 20:23:50.599 gcd[13765:2856357] block4
Concurrent Dispatch Queue
dispatch_queue_t myConcurretnDispatchQueue = dispatch_queue_create("com.example.gcd.myConcurretnDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurretnDispatchQueue, ^{ NSLog(@"blcok1"); });
dispatch_async(myConcurretnDispatchQueue, ^{ NSLog(@"block2"); });
dispatch_async(myConcurretnDispatchQueue, ^{ NSLog(@"block3"); });
dispatch_async(myConcurretnDispatchQueue, ^{ NSLog(@"block4"); });
...
输出:
2017-01-19 20:25:17.937 gcd[13811:2867322] blcok1
2017-01-19 20:25:17.937 gcd[13811:2867323] block3
2017-01-19 20:25:17.937 gcd[13811:2867325] block2
2017-01-19 20:25:17.937 gcd[13811:2867366] block4
...
在 iOS 6+ 和 Mac OS X 10.8+ ARC 可以管理GCD.在此之前,用下面代码释放gcd
dispatch_release(queue);
Main Dispatch Queue/Global Dispatch Queue
Main Dispatch Queue 是在主线程中执行的Dispatch Queue。 是Serial Dispatch Queue。
追加到 Main Dispatch Queue 的处理实在主线程的RunLoop 中执行,由于是在主线程中执行,因此要将用户界面更新等一些必须在主线程中执行的处理追加Main Dispatch Queue
。
Global Dispatch Queue 是所有应用程序能够使用的 Concurrent Dispatch Queue。没必要通过dispatch_queue_create 逐个生成Concurrent Dispatch Queue。只要的Global Dispatch Queue中使用即可。
// Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
/*
* Global Dispatch Queue 优先级从高到低
*/
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
下面是最常见的Main Dispatch Queue和Global Dispatch Queue的使用方法。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*
* 可并行的处理
*/
dispatch_async(dispatch_get_main_queue(), ^{
//只能在主线程中执行的处理
});
});
dispatch_set_target_queue
dispatch_set_target_queue函数是变更生成的Dispatch Queue
的优先级。
修改用户队列的目标队列,使多个serial queue在目标queue上一次只有一个执行。
// dispatch_set_target_queue(<#dispatch_object_t _Nonnull object#>, dispatch_queue_t _Nullable queue)
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
指定要变更执行优先级的Dispatch Queue 为 dispatch_set_target_queue函数的第一个参数,指定与要使用的执行优先级相同优先级的Global Dispatch Queue为第二个参数(目标)。第一个参数如果指定系统提供的Main Dispatch Queue 和 Global Dispatch Queue 则不知道会出现什么情况。因为你不能给系统的queue设置权限,所以这些均不可指定。
一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。
dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3 * NSEC_PER_SEC));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(33 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds");
});
dispatch_after 函数并不是在指定时间后执行,只是在指定时间追加处理到Dispatch Queue中。
第一个参数是指定时间用的dispatch_time_t 类型的值。使用dispatch_time 或者dispatch_walltimeb
第二个参数指定要追加处理的Dispatch Queuede,第三个参数指定执行处理的block。
Dispatch Group
在追加到Dispatch Queue
中的多个处理全部结束后执行结束处理。
- 将执行的处理全部追加到 一个
Serial Dispatch Queue
中,并在最后追加结束处理。 - 但是在并行队列中可以使用
Dispatch Group
实现。
Target:追加3个block
到Global Dispatch Queue
中,这些Block
如果全部执行完成,就会执行Main Dispatch Queue
中结束处理的Block
。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); });
dispatch_group_async(group, queue, ^{ sleep(3); NSLog(@"blk3"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk4"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"over");
});
解析: 向并行线程追加处理,多个线程并行执行,所以执行的顺序不定。 但是”over”
一定是最后执行的。另外,也可以使用dispatch_group_wait
函数,仅等待全部处理执行结束。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); });
dispatch_group_async(group, queue, ^{ sleep(3); NSLog(@"blk3"); });
dispatch_group_async(group, queue, ^{ NSLog(@"blk4"); });
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"over");
dispatch_group_wait
的二个参数是等待的时间。如果返回值为0,则表示所有的任务执行完毕。否则,group中仍有任务在执行。
dispatch_barrier_async
在访问数据库或文件时,使用Serial Dispatch Queue
能避免数据竞争的问题。
写入处理确实不能与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。
首先dispatch_queue
生成函数Concurrent Dispatch Queue
,在dispatch_async
中追加读取操作。
dispatch_queue_t = queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
dispatch_barrier_async(queue, blk_for_writing);
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_sync
dispatch_async
函数是非同步
的,即将指定的Block非同步
的加入到指定的Dispatch Queue
中,dispatch_sync
函数不做任何等待。
dispatch_sync
函数同步
的将Block追加到Dispatch Queue
中,在Block结束之前,函数会一直等待。
经常使用的一种情形是:执行Main Dispatch Queue
时,使用另外的线程Global Dispatch Queue
进行处理,处理结束后立即使用所得到的结果。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
/*
处理
*/
dispatch_sync(dispatch_get_main_queue(), ^{
// 主线程操作,如刷新UI
});
});
一旦调用dispatch_sync
函数,那么在指定的处理结束之前,该函数都不会返回。也是因为这个特性,在使用不当时,便会造成一个严重的问题,即死锁
。
例如在主线程中使用:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Hello????");
});
dispatch_apply
dispatch_apply
函数是dispatch_sync
和Dispatch Group
的关联API。
它按指定的次数将指定的Block
追加到指定的Dispatch Queue
,并等待全部处理执行结束。
dispatch_queue queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index){
NSLog(@"%zu",index);
});
NSLog(@"Done"); // 最后执行!
dispatch_suspend/dispatch_resume
dispatch_suspend
挂起指定的Dispatch Queue
。
‵‵‵
dispatch_suspend(queue);
‵‵‵
dispatch_resume
恢复指定的Dispatch Queue
。
dispatch_once
static int initialized = NO;
if (initialized == NO) {
/*
* 初始化
*/
}
static dispatch_once_t pred;
dispatch_once(&pred, ^{
/*
* 初始化
*/
});
通过dispatch_once
函数,代码即使在多线程环境下执行,也可保证百分之百安全。
第一组代码在大多数情况下也是安全的。但在多核CPU中,在正在更新的表示是否初始化的标志变量时,读取,就有可能多次执行初始化处理。而用dispatch_once
函数初始化就不必担心这样的问题。