iOS - GCD
概念
big
GCD (Grand Central Dispatch): 伟大的的中枢调节器; GCD 是苹果公司开发的一个创建子线程的一个方法,主要为多核的并行开发运算提出解决方案,利用 GCD 可以是的 CPU 的内核运用的更加充分,同时 GCD 也会自动管理线程中的生命周期。
small
1.队列:队列是一种线性表,队列的上下都是开口的与栈相反,队列中遵循的原则是FIFO即为先进先出,队列是在表尾进行添加操作,表头进行删除操作即先进先出后进后出。
2.同步:同步是指的是在调用方法时,按照顺序的执行一些代码。在第一个方法没有执行完的时候第二个方法是不会进行的。
3.异步:异步和同步相反,调用方法时,当没有收到第一个方法调用的返回值时,第二个方法也可以执行。
4.串行:程序运行时,程序会按照顺序执行代码,只是存在一个运行上下文。
5.并发:程序运行时,程序存在多个运行上下文,可以通过这些上下文执行不同的代码。
多线程编程
我们研究多线程编程之前,需要知道当我们执行一段代码的时候 CPU是如何执行的。
通过上图我们要了解一个概念叫做上下文切换,上下文切换是 OSX 和 iOS 的核心 XNU 内核在发生操作系统事件时会切换执行路径,例如 CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原 CPU 寄存器等信息,继续执行切换路径的 CPU 命令列。叫做上下文切换。
利用这种编程的方式叫做多线程编程,但是通过多线程编程同样的也会产生很多的问题具体问题如下
- 多线程更新相同的资源会导致数据的不一致。
具体表现为两个线程同时对一个数据进行更新,而两个线程得到的数据不相同从而导致数据的不一致性。 - 死锁问题,多个线程之间相互持有,造成持续等待。
如下代码就发生了线程的死锁,程序先执行 1,这时候同步线程会让程序进入等待等 2 执行完了之后再执行 3,但是 sync 有产生了队列,队列会将操作放到对尾 2 等到 3 执行完了在执行,而 3 又在等待 2 执行完了它在执行。所以造成了死锁现象。 - 过多的线程会消耗大量的内存。
因为过多的线程会使得 CPU 大量的调用“上下文切换”从而使得 CPU 的消耗巨大,从而导致程序的卡顿。
printf("1");
dispatch_sync(dispatch_get_main_queue(),^{
NSLog(@"线程发生了死锁");
printf("2");
})
printf("3");
GCD Code
dispatch_queue_create,dispatch_sync 和 dispatch_async
通过 dispatch_queue_create 函数可以生成 Dispatch Queue 根据参数的不同可以生成 Serial 和 Concurrent 两种类型的 Dispatch Queue。
dispatch_queue_t queue = dispatch_queue_create("www.kong.com",NULL);
..create后面的两个参数:<br>第一个参数是队列的名称<br>第二个参数传
NULL的时候是创建一个串行的队列。传
DISPATCH_QUEUE_CONCURRENT`的时候是创建一个并发队列。
这样我们分四种情况对 GCD 进行讨论,同步串行,同步并发,异步串行,异步并发.
** 同步串行**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
dispatch_sync(queue ,^{
NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 条件下我们需要将其进行释放。
打印的结果为
a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
创建了串行队列,通过同步的方式添加到Dispatch Queue 等待队列中,没有开辟新的线程,所有的打印都是在主线程中进行,队列 queue 按照 abc 的顺序添加到等待队列中,也就是 FIFO 原则,出队列的时候也是满足该原则实现了先进先出,后进后出,打印的时候按顺序进行打印。
** 同步并发**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue ,^{
NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 条件下我们需要将其进行释放。
打印的结果为
a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
创建了并发的队列添加到队列中的时候用 sync
同步的方式将其追加到Dispatch Queue 中 没有开辟新的线程,所有的数据处理仍然是在主线程中进行处理,Dispatch Queue 按照追加的顺序(队列 FIFO)的方式执行处理。
** 异步串行**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
dispatch_async(queue ,^{
NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 条件下我们需要将其进行释放。
打印的结果为
a当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)} (这里的 name = null,可以通过[NSThread currentThread]来设置)
b当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}
创建串行的Queue,然后通过异步的方式添加到队列中去,使得 Dispatch Queue开辟了一个新的线程,但是还是通过同步队列的方式来进行执行,都是在dispatch_async 创建出来的线程中按照顺序执行队列中的操作。
** 异步并发**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue ,^{
NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 条件下我们需要将其进行释放。
打印的结果为:
a当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
b当前的线程为 <NSThread: 0x147e51ec0>{number = 3, name = (null)}
创建的是并发队列,通过异步的方式将其追加到 Dispatch Queue 中,就会使得 CPU 开辟新的线程来执行代码块 Blcok 中的代码。从而达到程序流畅的目的。
GCD中其他的一些方法
** 利用 global 创建线程**
//通过dispatch_get_global_queue 创建的是并发的队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
执行耗时操作
});
*对于 main dispatch_queue 和 global dispatch_queue 执行 dispatch_retain 函数和 dispatch_release 函数不会引起任何的变化,也不会有任何的问题。
当我们使用 dispatch_queue_create 的时候要考虑到在 ARC 中何时执行 dispatch_retain 和 dispatch_release 两个函数方法。
** dispatch_after**
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"3秒后执行该方法");
});
通过这种方法延迟3秒执行,要知道这种方法的实现是在3秒后将 block 中的代码追加到 Dispatch Queue 中,并不是在指定的时间后进行处理。不像 NSTimer 里面的设置在什么时间后进行处理。
** 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(@"执行 block 1 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行 block 2 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行 block 3 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行 block 4 %@",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"执行完 group 执行的操作");
});
通过运用组的话,可以先执行组中的内容,然后当组中的内容执行完了之后用 group 的 notify 方法可以在执行 block 中的方法。这种方法用于顺序执行,例如下载图片的时候,下载多张图片,然后进行拼接到一起,时候可以用这种方法将两个图片都下载下来,然后在 group_notify中对图片进行拼接,然后得到结果。group_notify 也是起到一个追加的作用,将 block 中的内容追加到 Dispatch Queue 的后面。等到 group 中的队列执行完后在执行。
** dispatch_barrier_async**
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"向数据库中写入数据");
});
dispatch_barrier_async(queue, ^{
NSLog(@"从数据库中进行数据的读取");
});
dispatch_async(queue, ^{
NSLog(@"从数据库中写入数据");
});
通过 dispatch_barrier_async 方法可以使当前的线程只执行 barrier_async(block)中的操作在两次写入操作的时候如果不用 dispatch_barrier_async 方法的话就会导致读取数据的同时也在写入数据,就导致了获取的数据不准确,此时的 dispatch_barrier_async 就可以起到线程锁的作用,保障读写的安全。
** dispatch_apply**
NSArray *applyArray = @[@1,@2,@3,@4,@5,@6];
dispatch_apply([applyArray count], queue, ^(size_t index) {
NSLog(@"%zu %@",index,[applyArray objectAtIndex:index]);
});
这段代码是把一项任务提交到队列中多次执行,队列的串行并行由创建的队列所决定。这里类似一个循环遍历的功能,这样将不想关的循环调到后台线程执行,会将执行效率大大提高。
GCD 底层实现。
GCD 是用于管理追加 Block 的 C 语言层实现 FIFO队列
里面主要运用了 libdispatch,Libc(pthreads),XNU内核。
GCD中的 API 全部为包含在 libdispatch 库中的 C 语言函数,Dispatch Queue通过结构体和链表,被实现为 FIFO队列 ,FIFO队列管理是通过 dispatch_async 等函所追加的 Block。
Block 并不是直接加入 FIFO队列中,而是先加入 Dispatch Continuation 这一个 dispatch_continuation_t 类型的结构体中,然后在加入 FIFO队列,该 Dispatch Continuation 用于记忆 Block 所属的 DispatchGroup 和其他的一些信息。 当 Dispatch Queue 中执行 Block 的时候,libdispatch 从 Global Dispatch Queue 自身的 FIFO 队列中取出 Dispatch Continuation ,调用 pthread_workqueue_additem_up 函数。将该 Global Dispatch Queue 自身、符合其优先级的 workqueue 信息以及为执行 Dispatch Continuation 的回调函数等传递给参数。 workqueue 最终决定是否生成线程。
workqueue 的线程执行 pthread_workqueue 函数,该函数调用 libdispatch 的回调函数,在该回调函数中执行加入到 Dispatch Continuation 的 Block。
Block 执行结束后,进行通知 DispatchGroup 结束,释放 Dispatch Continuation 等处理,开始准备执行加入到 Global Dispatch Queue中的下一个 Block。
说明:
本文参考
图书《iOS与 OSX 多线程和内存管理》