什么是线程?####
当一个应用程序安装到 Mac 或 iPhone上的时候, Mac、iPhone 的操作系统 OSX, iOS 会根据用户的姿势启动该应用程序后,一个一个地执行CPU命令行,先执行一个命令,再接着执行另一个命令,这样不断的循环下去。
CPU 一次只能执行一个命令, 不能执行某处分开的并行的两个命令,因此通过 CPU 执行的 CPU 命令就好比一份无分叉的大道,其执行不会出现分歧。所以,1个CPU 执行的CPU命令列为一条无分叉路经
即为线程
;
但,这种无分叉路经不只一条,存在多条时候,即为多线程,1 个 CPU 核执行多条不同路径上的不同命令。 虽然CPU相关技术很多, 但基本上1个CPU核一次能够执行的CPU命令始终为1。 OS X和 iOS 的内核在发生操作系统事件的时候,会切换执行路经,执行中路经的状态,例如CPU的寄存器等信息保存到各自路经专用的内存中,从切换目标路经专用的内存中复原CPU寄存器等信息,继续执行切换路经的 CPU 命令行,这个被称为`上下文切换`;因而其实线程之间一直进行着上下文切换。
多线程容易出现问题,如 多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待时间的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。
即使有问题,也要采用多线程,这样才能保证应用程序的响应性能,启动后, 最先执行的线程(主线程)描绘界面、处理触摸屏幕的时间等等,若是在主线程中进行大量的数据操作便会阻塞主线程,从而导致没法再更新用户界面,出现长时间卡顿。
GCD大大简化了偏于复杂的多线程的源代码。
什么是GCD?####
GCD是异步执行任务的技术之一, 一般将应用程序中记述的线程管理用的代码在系统级中的实现,开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD 就能生成必要的线程并计划执行任务,由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
也就是说,GCD 用我们难以置信的非常简洁的记述方法,实现了极为负责繁琐的多线程编程,可以说这是一项划时代的技术。
dispatch_async(queue, ^{
// 这一行的代码表示处理在后台线程执行
/*
* 长时间处理的工作
* 如一些复杂的数据处理
*/
dispatch_async(dispatch_get_main_queue(),^{
// 这代码表示在主线程中执行
/*
* 只在主线程可以执行的处理
* 例如用户界面更新
*/
});
});
Disaptch Queue#####
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
就如同:
dispatch_async(queue, ^{
/*
* 想执行的任务
*/
})
使用了 Block 语法“定义想执行的任务”,通过 dispatch_async 函数“追加”赋值在变量 queue 的 “Dispatch Queue 中”,这样就可以指定 Block 在另一个线程中执行。“”Dispatch Queue 按照追加的顺序(FIFO)执行处理。
在执行处理时候,存在两种 Disparch Queue, 一种是等待执行中处理的 Serial Dispatch Queue, 另一种是不等待现在执行中处理的 Concurrent Dispatch Queue。
dispatch_async(queue, block0);
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);
dispatch_async(queue, block4);
dispatch_async(queue, block5);
如以上代码,当 queue 为 Serial Dispatch Queue 时候,因为要等待现在执行中的处理结果,所以顺序为
block0
block1
block2
block3
block4
block5
当变量 queue 为 Concurrent Dispatch Queue 时,因为不用等待现在执行中的处理结束,所以执行了 block0 不不管其是否执行结束都执行 block1, 如此重复。
iOS 和 OS X 的核心 --XNU 内核决定应当使用的线程数,并只生成所需要的线程执行处理,当处理结束,应当执行的处理数减少时,XNU 内核会结束不再需要的线程。
dispatch_queue_create#####
通过 Dispatch_queue_create
函数生成 Dispatch Queue。
dispatch_queue_t mySyrialQueue = dispatch_queue_create("com.queue.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(mySyrialQueue, ^{
NSLog(@"mySyrialQueue block");
});
// 第一个参数指定 Queue 的名称,建议使用应用程序 ID 这种命名方式, 当然也可以设置为 NULL;
// 第二个参数指定 DISPATCH_QUEUE_SERIAL 或 NULL 表示 生成 Serial Dispatch Queue
虽然 Serial Dispatch Queue 同时只能追加一个处理,但当生成多个 Serial Dispatch Queue 时,各个 Serial Dispatch Queue 将并行执行,即同时执行多个。系统对于一个 Serial Dispatch Queue 就只能生成并使用一个线程, 如果生成2000个 Serial Dispatch Queue, 那么就可以生成2000多个线程。如果过多的使用线程,就会消耗大量的内存,引起大量的上下文切换,大幅度降低系统的响应性能。
所以, 尽量在“避免多个线程更新相同资源导致数据竞争时”使用 Serial Dispatch Queue
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.queue.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentQueue, ^{
NSLog(@"myConcurrentQueue block");
});
// 第一个参数指定 Queue 的名称,建议使用应用程序 ID 这种命名方式, 当然也可以设置为 NULL;
// 第二个参数指定 DISPATCH_QUEUE_CONCURRENT 生成 Concurrent Dispatch Queue
Main Dispatch Queue/Global Dispatch Queue
除了创建线程之外, 系统还提供了标准的 Dispatch Queue, 就是 Main Dispatch Queue 和 Global Dispatch Queue。
Main Dispatch Queue
主线程, 是一个 Serial Dispatch Queue,追加到主线程的处理在主线程的 RunLoop 中执行, 由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到 Main Dispatch Queue 使用。-
Global Dispatch Queue
Global Dispatch Queue 是所有的应用程序都能使用的 Concurrent Dispatch Queue,没有必要通过 dispatch_queue_create 函数来逐个生成 Concurrent Dispatch Queue。只要获取即可。它包含四个执行优先级。- 高优先级(High Priority)
- 默认优先级(Default Priority)
- 低优先级(Low priority)
- 后台优先级(Background Poriority)
在向 Global Dispatch Queue 追加处理的时候,应选择与处理内容对应的执行优先级的 Global Dispatch Queue。但是并不能保证实时性,因此优先级只是大致的判断。
// Main Dispatch Queue 的获取方法
dispatch_queue_t mainDisatchQueue = dispatch_get_main_queue();
// Global Dispatch Queue(高优先级)的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
DISPATCH_QUEUE_PRIORITY_HIGH // 高优先级
DISPATCH_QUEUE_PRIORITY_LOW // 低优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT // 默认优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND // 后台优先级
// 使用 Main Dispatch Queue 和 Global Dispatch Queue 源码
/*
* 在默认的优先级的 Global Dispatch Queue 中执行 block
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
// 可并行执行的处理
// 在 main 中block
dispatch_async(dispatch_get_main_queue(), ^{
// 在 主线程中 执行 Block
});
});
dispatch_after#####
- 在指定时间后执行处理的情况,可以使用 dispatch_after, 需要注意的是 dispatch_after 并不是在指定时间后执行处理,而是在指定时间追加处理到 Dispatch Queue。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
// ull为C语言数值字面量。
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three secons");
});
// 第一个参数 指定时间用的 dispatch_time_t 类型的值 使用 dispatch_time 函数或者 dispatch_walltime 函数构成
// dispatch_time_t 表示相对时间, dispatch_walltime 表示绝对时间
以上代码,因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以必入每隔 1/60 秒执行的 RunLoop 中, Block 最快在3秒后执行,最慢也在3+1/60秒后执行,
- dispatch_time_t 函数
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
函数获取从第一个参数 制定的时间开始到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是 `DISPATCH_TIME_NOW` 表示现在的时间。 "ull"表示 C 语言的数字面量(表示“unsigned long long”)
NSEC_PER_MSEC 表示以毫秒为单位
NSEC_PER_SEC 表示为秒
Dispatch Group#####
在追加到 Dispatch Queue 中的多个处理全部结束后限制性结束处理, 这种情况经常出现, 当为 Serial Dispatch Queue 时候, 只要将想要执行的处理全部追加到 Serial Dispatch Queue 中并在最后追加结束处理即可实现,但是要使用 Concurrent Dispatch Queue 时或同事使用多个 Dispatch Queue 时候,源码就会变得颇为复杂。
此种情况适合使用 Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
// 执行结果
block1
block2
block3
done
因为向 Global Dispatch Queue 即 Concurrent Dispatch Queue 追加处理,多个线程并行处理执行,所以追加处理的执行顺序不定,执行时候会发生变化,但是此执行结果的 done 一定是最后输出的。
无论向什么样的 Dispatch Queue 中追加处理, 使用 Dispatch Group 都可监视这些处理执行的结束,一旦监测到所有的处理执行结束, 就可以结束的处理追加到 Dispatch Queue 中, 这就是使用 Dispatch Group 的原因。
dispatch_group_notify 函数第一个参数为指定的要监视的 Dispatch Group. 在追加到该 Dispatch Group 的全部处理执行结束后,将第三个参数的 block 追加第二个参数的 Dispatch Queue 中, 在 dispatch_group_notify 函数中不管指定什么样的 Dispatch Queue。属于 Dispatch Group 的全部处理在追加到指定的 Block 时都已执行结束。
除了 dispatch_group_async 之外,还可以使用dispatch_group_wait 仅等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});
NSInteger i = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 如果 i = 0 表示处理结束, 反之 Group 并没执行完。
第二个参数,为 dispatch_time_t 类型,为等待的时间, 假设block1里执行了复杂的耗时操作超过一秒, 当第二个参数为小于1 秒时, 这 `dispatch_group_wait` 返回的 肯定不为0 则表示为 Group 并没执行完, 假设第二个参数为 `DISPATCH_TIME_FOREVER` ,意味永久等待,所以只要 Group 的处理尚未执行结束, 就会一直等待, 中途不能取消。 也就 `dispatch_group_wait` 返回的值也一定为0;
dispatch_barrier_async#####
访问数据库或者文件的时候,如前所述,使用 Serial Dispatch Queue 可避免数据竞争的问题。 写入数据处理不可与其他写入数据处理以及包涵读取处理的其它某些处理并行执行,但是如果读取处理
只是与读取处理
并行执行, 那么多个并行执行就不会发生问题。
首先,用 dispatch_queue_create 函数生成 Concurrent Dispatch Queue, 在 dispatch_async 中追加读取处理。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block0_for_reading);
dispatch_async(queue, block1_for_reading);
// 进行写入操作
dispatch_async(queue, block0_for_writing);
//
dispatch_async(queue, block2_for_reading);
dispatch_async(queue, block3_for_reading);
若按此代码执行, 在 bock1 与 block2 之间执行写入操作,就可能会在追加写入之前的处理中读取到与期待不符合的数据,还可能因为非法的访问导致应用程序一场。若采用以下操作:
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block0_for_reading);
dispatch_async(queue, block1_for_reading);
// 进行写入操作
dispatch_barrier_async(queue, block0_for_writing);
//
dispatch_async(queue, block2_for_reading);
dispatch_async(queue, block3_for_reading);
则会有效的防止出现的问题,因为 dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中,然后由 dispatch_barrier_async 函数追加的处理执行完毕之后,Concurrent Dispatch Queue 的处理又开始执行。