iOS GCD
一、GCD是异步执行任务的技术之一,一般将应用程序中记叙的线程管理用的代码在系统级中是实现。
开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
二、多线程编程
由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像一个CPU核能够并行的执行多个线程一样,而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行任务的多个线程的技术。
多线程编程容易发生各种编程问题:比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量的内存资源等等。
尽管会有问题,但是为什么还要用呢?
在应用程序启动时,通过最先执行的线程,即主线程来描绘用户界面、处理用户触摸事件等,但是如果在该主线程中进行长时间的处理,就会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能跟新用户界面,应用程序的画面长时间停滞等问题。
三、GCD 的 API
1.Dispatch Queue
开发者要做的只是定义想要执行的任务并追加到适当的DispatchQueue中。
在Block中定义想要执行的任务,然后追加到Dispatch Queue中
Dispatch Queue是执行处理的等待队列,通过dispatch_async等API,在Block语法中记叙想要执行的处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序(FIFO)执行处理。
另外,在执行处理时存在两种Dispatch Queue:
Dispatch Queue的种类 |
说明 |
Serial Dispatch Queue |
等待现在执行中处理结束 |
Concurrent Dispatch Queue |
不等待现在执行中处理结束 |
解释说明一下:
(1) Serial Dispatch Queue:就是要等待现在执行中处理结束后才可以进行下一个任务的执行处理,假如现在有blk1,blk2,blk3,在Serial Dispatch Queue中,那么同时执行处理数只能是一个,而且按按添加顺序FIFO进行处理,即先执行blk1,执行结束后再执行blk2,执行结束再进行blk3的执行。
(2) Concurrent Dispatch Queue:就是一个线程的执行不等待现在(当前)执行中的任务处理结束就可以开始另一个任务的执行处理。同样假如有blk1,blk2,blk3在Concurrent Dispatch Queue中,那么首先执行blk1,不管blk1是否执行处理结束,都开始执行后面的blk2,不管blk2是否执行结束,都开始执行后面的blk2。
这样虽然不用等待处理结束,可以并行执行多个任务处理,但是并行处理数取决于当前系统的状态,有它决定Concurrent Dispatch Queue中并行执行的处理数。所谓并行执行就是使用多个线程来同时执行多个处理任务(block中的执行任务)。
SerialDispatchQueue同时只能执行一个追加处理
ConcurrentDispatchQueue并行执行多个追加处理
虽然SerialDispatchQueue ConcurrentDispatchQueue受到系统资源的限制,但是用dispatch_queue_create可以生成任意多个Dispatch Queue
当生成多个SerialDispatchQueue时,各个SerialDispatchQueue将并行执行,虽然一个SerialDispatchQueue同时只能执行一个追加处理,但是如果将处理分别追加到4个
Serial Dispatch Queue中,各个Serial Dispatch Queue执行一个,即为同时执行4个处理
但是生成Serial Dispatch Queue的个数受系统限制
为了避免多线程编程的问题之一---数据竞争,就可以使用Serial Dispatch Queue。
当想并行执行且不发生数据竞争等问题时就应该使用Concurrent Dispatch Queue。
//以下代码是两种生成Serial Dispatch Queue的方式 dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("cn.edu.scnu.mySerialDispatchQueue", NULL); // dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("cn.edu.scnu.mySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_create,该方法中的第一个参数指定SerialDispatchQueue的名称,DispatchQueue的名称推荐使用应用程序ID之中逆序全程域名,第二个参数指定为NULL(或者DISPATCH_QUEUE_SERIAL)时即表示生成的是Serial Dispatch Queue,指定为DISPATCH_QUEUE_CONCURRENT时即表示生成的是Concurrent Dispatch Queue
//以下代码生成ConcurrentDispatchQueue dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("cn.edu.scnu.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(myConcurrentDispatchQueue, ^{ NSLog(@"block on my ConcurrentDispatchQueue"); });
2、MainDispatch Queue 和 Global Dispatch Queue
实际上不用特意生成DispatchQueue,系统也会提供几个给我们,就是Main DispatchQueue 和 Global Dispatch Queue
Main Dispatch Queue就是主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main DispatchQueue自然就是 Serial Dispatch Queue
//获取Main Dispatch Queue dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
GlobalDispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue,没有必要通过dispatch_queue_create方法逐个生成Concurrent Dispatch Queue,只要获
取GlobalDispatch Queue使用即可。
Global Dispatch Queue有四个优先级:High Priority,Default Priority,Low Priority,Background Priority
//获取高优先级的的Global Dispatch Queue /* DISPATCH_QUEUE_PRIORITY_HIGH DISPATCH_QUEUE_PRIORITY_DEFAULT DISPATCH_QUEUE_PRIORITY_LOW DISPATCH_QUEUE_PRIORITY_BACKGROUND */ dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); //第一个参数指定Global Dispatch Queue的优先级,第二个参数指定为0
3、dispatch_set_target_queue
dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都是使用与默认优先级的Global Dispatch Queue相同执
行优先级的线程
如果想变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue方法。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("cn.edu.scnu.mySerialDispatchQueue", NULL); dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueue); //指定要变更执行优先级的dispatch queue为dispatch_set_target_queue方法的第一个参数,指定与要使用的执行优先级相同优先级的Global Dispatch Queue为第二个参数(目标) //第一个参数不可以指定为系统提供的Main Dispatch Queue 和 Global Dispatch Queue
4、dispatch_after
想在指定时间后执行处理的情况,可以使用 dispatch_after 方法来实现
double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"waited at least 2.0 seconds."); });
值得注意的是,dispatch_after方法并不是在指定时间后执行处理任务,而是在指定时间后追加处理到dispatch queue中,上面的代码在2秒后用dispatch_after方法追加
block到Main Dispatch Queue中,因为Main Dispatch Queue在主线程的RunLoop中执行,所以比如每个1/60秒执行的RunLoop中,Block最快在2秒后执行,最慢在2秒+1/60
秒后执行,而且在Main Dispatch Queue中又大量处理追加或者主线程的处理本身有延时时,这个时间会更长。
dispatch_after这个方法的第二个参数指定要追加的dispatch queue,第三个参数指定要执行处理的Block,第一个参数是指定时间用的dispatch_time_t类型的值,在使用
dispatch_after的时候,编译器会自动帮你生成这些代码,只需修改delayInSeconds就可以了。
5、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_HIGH, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{NSLog(@"blk1");}); dispatch_group_async(group, queue, ^{NSLog(@"blk2");}); dispatch_group_async(group, queue, ^{NSLog(@"blk3");}); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done"); });
因为向 Global Dispatch Queue即Concurrent Dispatch Queue追加处理任务,多个线程并行执行,所以追加处理任务的执行顺序是不定的,执行时顺序会发生变化,但是主线程中执行结果输出done肯定是最后的。
下面稍微解释一下上面的那段代码,上面由3个输出任务的block组成一个dispatch group,并把这个dispatch group添加到dispatch queue中执行,当dispatch group中的
block任务执行完毕后,dispatch_group_notify方法就会被执行到,所以它的第一个参数是group,表示其被监视。在追加到dispatch group中的全部执行处理任务执行结束后,
将第三个参数中的block任务添加到第二个参数的dispatch queue中执行,注意此时dispatch group中的所以执行任务已经执行结束了。
另外,在dispatch group中也可以使用 dispatch_group_wait方法仅等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{NSLog(@"blk1");}); dispatch_group_async(group, queue, ^{NSLog(@"blk2");}); dispatch_group_async(group, queue, ^{NSLog(@"blk3");}); dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
在这个方法中的第二个参数指定等待时间,这里使用DISPATCH_TIME_FOREVER意味着永久等待,只要属于dispatch group中的处理尚未执行结束,就会一直等待,中途不能取消。
当然如同dispatch——after方法中那样,也可以指定等待时间为1秒等等。
//这里指定等待时间1s,即1s后查看dispatch group中的处理是否全部执行结束 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC); long result = dispatch_group_wait(group, time); if (result == 0) { //属于dispatch group中的全部处理都执行结束 } else { //属于dispatch group的某一个处理还在执行 }
//这里也可以指定DISPATCH_TIME_NOW,则不用任何等待即可判断属于dispatch group中的处理是否全部执行结束 long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
6、dispatch_barrier_async
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_async(queue, ^{ //表示执行数据读取任务 NSLog(@"blk1_reading"); }); dispatch_async(queue, ^{ NSLog(@"blk2_reading"); }); dispatch_async(queue, ^{ //表示执行数据写入处理任务 NSLog(@"blk1_writting"); }); dispatch_async(queue, ^{ NSLog(@"blk3_reading"); }); dispatch_async(queue, ^{ NSLog(@"blk4_reading"); });
如果像上面那样简单的在dispatch_async方法中添加写入数据处理的任务,那么根据Concurrent Dispatch Queue并行执行的性质,就很有可能不是按照上面的添加处理任务的
顺序执行,那么在blk3_reading 和 blk4_reading执行读取数据的时候,blk1_writting进行写入数据的处理还没有执行到,那么后两次的读取数据操作读取到的数据就与期望中
的不符了。
解决这个问题的处理就是 使用 dispatch_barrier_async
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_async(queue, ^{ //表示执行数据读取任务 NSLog(@"blk1_reading"); }); dispatch_async(queue, ^{ NSLog(@"blk2_reading"); }); dispatch_barrier_async(queue, ^{ //表示执行数据写入处理任务 NSLog(@"blk1_writting"); }); dispatch_async(queue, ^{ NSLog(@"blk3_reading"); }); dispatch_async(queue, ^{ NSLog(@"blk4_reading"); });
使用dispatch_barrier_async方法,它会等待在它之前添加到 Concurrent Dispatch Queue的所有处理执行结束后,才执行该处理,然后等待该处理结束后,才接着处理后续添
加到Concurrent Dispatch Queue中的处理任务。当然在dispatch_barrier_async方法之前和之后添加的处理任务可以并发执行,即不保证执行顺序,但是可以确保
dispatch_barrier_async方法添加的任务一定是只能同时执行一个,按其添加任务顺序执行的,就是说,执行完blk1_reading和blk2_reading的读取数据任务后,才是进行
blk1_writting的写入数据任务,然后才是执行接着的读取数据的任务。
7、dispatch——sync
dispatch_async方法中的async意味着“非同步”,就是将指定的block非同步的添加到dispatch qeueue中,dispatch_async方法不做任何等待。
dispatch_sync方法中的sync意味着“同步”,也就是将指定的block同步追加到dispatch queue中,在追加block的过程结束之前,dispatch_sync方法会一直等待。
一旦调用dispatch_sync,那么在指定的处理执行结束之前,该方法不会返回,dispatch_sync方法可以简化代码,也可以说是简易版的dispatch_group_wait方法
dispatch_sync方法使用简单,但是容易引起死锁
dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ NSLog(@"引起死锁!"); });
上面的代码在主线程中执行指定的block,并等待其执行结束,但是其实在主线程中就是在执行这些代码,所以就造成了死锁。
未完待续!