iOS 多线程编程gcd全面系统认识
这两天在看《OC高级编程-多线程编程和内存管理》日本人写的那本,该书对arc,block和gcd有了更深层次的解读,非常不错。现在总结一下gcd相关的知识。有关arc和block的参考arc 参考block
网上很多博客都对gcd有过讲解,很多是对gcd的全局队列,主线程队列,创建队列等等,做了单方面的描述,不是很全面系统。下面我们将学习一下系统得gcd。本文主要分为下面几个要点,前几个好点比较好理解,最后可能理解起来有些费劲!
● 什么是gcd,iOS为什么要用多线程
● 创建线程,序列线程和并发线程
● 系统默认的五个队列
● gcd的其他接口
● gcd的实现和dispatch source
下面开始介绍第一个要点
1. 什么是GCD
gcd是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务,并追加到适当的Dispatch Queue中,gcd就能生成必要的线程并计划执行任务。由于线程管理是系统级实现的。因此可以统一管理,可以执行任务,这样就比以前的线程更加有效——摘自Apple官方文档。
在gcd出现之前,就有performSelector还有NSThread。但是performSelector比NSTread要简单,gcd比performSelector更加简单,一目了然。
本书中给线程下了一个定义:1个CPU执行的CPU指令列为一条无分叉路径即为“线程”,如下图:
多线程就是一个程序中有好几个这样的无分叉路径,如下图
但是多线程是极易发生各种问题的技术,例如数据竞争,死锁,线程耗费大量内存等等。虽然极易出现问题,但是也应当使用多线程。因为多线程可以保证应用程序的响应性能。
在iOS中App启动时,最先执行的线程就是主线程,它用来绘制UI,触摸屏幕的事件。如果在主线程中进行长时间的处理,就妨碍主线程的执行,从而导致UI卡顿。如下图
2. 创建线程队列
一般情况下,不需要手动创建线程队列,因为系统为了我们准备了2个队里(见下个要点)。
这要说明一下Dispatch Queue,它是执行处理的等待队列。Dispatch Queue有两种类型,一个是Serial Dispatch Queue顺序队列,一个是Concurrent Dispatch Queue。这两个都很好理解,前者是串行队列,一个任务执行完毕,接着下个任务执行。Concurrent Dispatch Queue是并发队列,
请看下面的代码:
//dispatch_queue_t gcd = dispatch_queue_create("这是序列队列", NULL); dispatch_queue_t gcd = dispatch_queue_create("这是并发队列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(gcd, ^{NSLog(@"b0");}); dispatch_async(gcd, ^{NSLog(@"b1");}); dispatch_async(gcd, ^{NSLog(@"b2");}); dispatch_async(gcd, ^{NSLog(@"b3");}); dispatch_async(gcd, ^{NSLog(@"b4");}); dispatch_async(gcd, ^{NSLog(@"b5");}); dispatch_async(gcd, ^{NSLog(@"b6");}); dispatch_async(gcd, ^{NSLog(@"b7");}); dispatch_async(gcd, ^{NSLog(@"b8");}); dispatch_async(gcd, ^{NSLog(@"b9");}); dispatch_async(gcd, ^{NSLog(@"b10");}); dispatch_release(gcd);使用不同的Queue输出结果是不同的。如果是顺序队列,输出结果肯定是顺序的,如果使用并发队列,每次都不一样,下面是其中一个log:
b1
b0
b4
b3
b2
b5
b6
b7
b8
b9
之所以Concurrent Dispatch Queue可以做到并发执行,是因为其使用了多个线程,就上面的输出,可能的方案如下:
刚才的代码中已经使用dispatch_queue_create函数,看一下dispatch_queue_create的原型:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);这是一个C语言级别的函数。如果第二个参数是NULL表示顺序队列,如果是DISPATCH_QUEUE_CONCURRENT则是并发队列。通常一个多线程更新相同资源导致数据竞争的时候使用顺序队列,当想并行不发生数据竞争等问题的处理时,使用并发队列。
注意:Dispatch Queue必须有程序员来释放。因为ARC不会应用到派发队列上。可以在create后立即调用dispatch_release();因为block持有这个队列。当block运行完毕,这个队列就自动释放了。
3. 系统默认的五个队列
实际上,系统会为我们创建几个队列,他们是Main Dispatch Queue和Global Dispatch Queue。系统提供的Dispatch Queue总结如下表
下面是获取全局并发队列和主线程队列的代码
//获取全局队列 dispatch_queue_t mainQ = dispatch_get_main_queue(); //获取高,中,低,后台优先级队列并发队列 dispatch_queue_t globalH = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_queue_t globalD = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t globalL = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_queue_t globalB = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
4. gcd的其他接口
dispatch_set_target_queue——改变用dispatch_queue_create创建的队列的优先级
dispatch_after——延时处理一段代码
dispatch_group——并发队列中,所有的任务执行完成后,调用的代码
dispatch_barrier_async——栅栏作用。可以将并发队列中任务分成两部分。
dispatch_sync——同步等待,当前队列全部执行完毕
dispatch_apply——规定次数将指定block加入到dispatch_queue中,并等待全部处理执行结束。
dispatch_suspend/dispatch_resume——挂起恢复指定线程队列
dispatch_semaphore——从名字中可以发现“信号量”,该接口是对dispatch_barrier_async精细化处理
dispatch_once——只执行一次的代码。通常用于单例
dispatch I/O——如果想提高文件读取速度,可以尝试dispatch I/O
具体的使用参考下面的代码。
-(void) testGCD{ [self testDispatch_target]; [self testDispatch_after]; [self testDispatch_Group]; [self testDispatch_Barrier]; [self testDispatch_sync];//在运行的时候,将这一行注释掉,不然就死锁了 [self testDispatch_apply]; [self testDispatch_once]; } /* 可以改变dispatch_queue的优先级 */ -(void) testDispatch_target{ dispatch_queue_t serial = dispatch_queue_create("xxxx",NULL); dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_set_target_queue(serial, queueG); } /* testDispatch_after 延时添加到队列 */ -(void) testDispatch_after{ dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"3秒后添加到队列"); }); } /* dispatch_barrier_async 栅栏的作用 */ -(void) testDispatch_Barrier{ //dispatch_queue_t gcd = dispatch_queue_create("这是序列队列", NULL); dispatch_queue_t gcd = dispatch_queue_create("这是并发队列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(gcd, ^{NSLog(@"b0");}); dispatch_async(gcd, ^{NSLog(@"b1");}); dispatch_async(gcd, ^{NSLog(@"b2");}); dispatch_async(gcd, ^{NSLog(@"b3");}); dispatch_async(gcd, ^{NSLog(@"b4");}); dispatch_barrier_async(gcd, ^{NSLog(@"barrier");});//dispatch_barrier_async dispatch_async(gcd, ^{NSLog(@"b5");}); dispatch_async(gcd, ^{NSLog(@"b6");}); dispatch_async(gcd, ^{NSLog(@"b7");}); dispatch_async(gcd, ^{NSLog(@"b8");}); dispatch_async(gcd, ^{NSLog(@"b9");}); dispatch_async(gcd, ^{NSLog(@"b10");}); dispatch_release(gcd); } /* dispatch_sync.的三个操作 */ -(void) testDispatch_sync{ //1. 同步等待 dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_sync(queueG, ^{NSLog(@"dispatch_sync同步等待");}); //2. 死锁 dispatch_queue_t mainQ = dispatch_get_main_queue(); dispatch_sync(mainQ, ^{NSLog(@"dispatch_sync同步等待,这么写是死锁");}); //3. 同样是死锁 dispatch_sync(mainQ, ^{ dispatch_sync(mainQ, ^{NSLog(@"dispatch_sync同步等待,同样是死锁");});}); } /* dispatch Group的演示 */ -(void) testDispatch_Group{ dispatch_queue_t mainQ = dispatch_get_main_queue(); dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queueG, ^{NSLog(@"dispatch group blk1");}); dispatch_group_async(group, queueG, ^{NSLog(@"dispatch group blk2");}); dispatch_group_notify(group, mainQ, ^{NSLog(@"dispatch group");}); dispatch_release(group); } /* 按照指定次数将指定的block追加到指定的dispatch queue中。 */ -(void) testDispatch_apply{ dispatch_queue_t queueG = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queueG, ^(size_t i){NSLog(@"%zu",i);}); NSLog(@"done"); //经典的做法是,循环一个数组 NSArray* array = [NSArray arrayWithObjects:@1,@2,@3, nil]; dispatch_apply([array count], queueG, ^(size_t i){ NSLog(@"%ld", [array[i] integerValue]); ;}); } /* 执行一次 */ -(void) testDispatch_once{ static dispatch_once_t p; dispatch_once(&p,^{ NSLog(@"testDispatch_once"); ;}); }
dispatch_suspend/dispatch_resume、dispatch_IO、dispatch_semaphore 这几个不太常用,就不再过多解释了
5. gcd的实现和dispatch source
本书中对gcd的实现不清楚,比较笼统和模糊。下面是一些介绍,gcd的实现依赖下面几个知识:
● 用于管理追加的Block的C语言层实现的FIFO队列
● Atomic函数中实现的用于排他控制的轻量级信号
● 用于管理线程的C语言实现的一些容器
当然除了上面说的工具外,gcd还需要内核级的一些实现。系统级中的一些软件组件比如:libdispatch实现Dispatch queue,Libc(pthreads)实现pthread_workqueue,XNU内核实现workqueue。
编程人员使用的gcd全部API都包含在libdispatch库中的c语言函数。dispatch queue通过结构体和链表实现FIFO队列,该队列管理这追加的block。
block并不是直接追加到FIFO中,而是先加入dispatch continuation这一dispatch_continuation_t类型结构体中,然后再假如FIFO队列。dispatch continuation用于记录block所属的一些信息,类似于执行上下文。
本书中以下部分描述了global dispatch queue 、Libc pthread_workqueue和XNU workqueue。书中的意思是,依次逐级调用。
下面说一下dispatch source
gcd中除了dispatch queue以外,还有不太引人注目的dispatch source 。它是BSD系惯有功能kqueue的包装。kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其cpu负荷小,尽量不占用资源。kqueue是应用程序处理XNU内核中发生的各种事件方法中最优秀的一种。
dispatch source可以取消,而dispatch queue不可以取消。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。