一、GCD的概念
1、什么是GCD
什么是GCD? 顾名思义,Grand Central Dispatch 是异步执行任务的技术之一,一般应用程序中记述的线程管理用的代码在系统中的实现。开发者需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务,由于线程的管理是作为系统的一部分来实现的。因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
/** 执行后台线程 */-(void)lunchThreeByNSObject_performSelectorInBackground{ [self performSelectorInBackground:@selector(doWork) withObject:nil];}
/**
后台线程处理方法
*/
-(void)doWork{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
/**
长时间处理。
比较耗时的操作
*/
[self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
[pool drain];
}
/** 主线程处理方式 */-(void)doneWork{ /** 刷新相关用户界面 */}
像这种传统的执行方式如果换做是GCD话,不仅代码量少,并且效率还很高效
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ /** 长时间处理。 比较耗时的操作 */ dispatch_async(dispatch_get_main_queue(), ^{ /** 刷新相关用户界面 */ }); });
2、多线程编程
在这里就不必介绍线程的概念了,想必大家都知道相关的线程和进程的概念以及二者之间的区别和联系。iOS程序执行命令的时候,执行命令列的地址会远离当前位置,叫做位置迁移,但是一个CPU一次只能执行一个命令,不能执行的某处分开的并列的两个命令,因此通过CPU执行CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。
![](https://img.haomeiwen.com/i5015685/201d28a04143191d.png)
这里所说的一个CPU执行的CPU命令列为一条无分叉的大道,即为“线程”.,然而计算机现在都是多内核的。即可以同时存在多个多核CPU执行多条指令,XNU内核发生操作事件时,会切换执行路劲,从切换目标专用的内存块中,复原CPU寄存器的信息,继续执行切换路径的CPU命令,这成为上下文切换,由于使用多线程的程序可在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能并列的执行多个线程一样,而且在多个CPU的情况下,就不是看上去了,而真的提供了多个CPU核并行执行多个线程的技术。
这种利用多线程的编程技术就叫多线程编程。
然而多线程编程的过程中会遇到相关的问题,1,数据竞争,2,相互抢占资源即死锁,3,会消耗大量的内存,虽然存在这些问题,但是任然推荐使用多线程的原因是因为,使用多线程可保证应用程序的响应能力。如果再主线程中操作比较耗时的操作,此时会严重的阻碍主线程的执行,从而不能更新用户界面,应用程序会变得相当的卡顿。
二、GCD的核心用法
1、Dispatch Queue的介绍
dispatch queue 是什么饿,顾名思义,就是执行处理的等待队列,应用程序通过dispatch_async 函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中,并按照FIFO的顺序执行处理。
Dispatch Queue 的种类。分为两种,一种是 Serial Dispatch Queue 另一种是 Global Dispatch Queue。Serial Dispatch Queue会等待现在执行的处理结束,而 Global Dispatch Queue则不会等待,因此 Global Dispatch Queue被称作并行队列,Serial Dispatch Queue被称作串行队列。
![](https://img.haomeiwen.com/i5015685/76e8997bac758c25.png)
当Queue的类型在线程中是Serial Dispatch Queue时,因为需要等待在执行中的处理结束,所以就插入Block Queue中的任务是按顺序执行,如果上一个没有执行完成,则需要等待。并且执行的同时,只能有一个任务在执行,执行代码如下。
![](https://img.haomeiwen.com/i5015685/a3b6d9832cef38f9.png)
当Queue的类型是Global Dispatch Queue时,因为不用等待现在执行的任务处理结束,所以首先执行1,不论1是否执行完毕,都要开始执行2,如此重复循环,这样虽然不用等待处理结束,可以并行执行多个任务,但是执行的数量取决于当前的系统状态。
![](https://img.haomeiwen.com/i5015685/7bc7ba72feabdc75.png)
2、Dispatch_queue_create的介绍
顾名思义,此API 是用来创建Queue 的。只要指明相关的类型,可以有目的的生成你项目中想要的Queue类型
![](https://img.haomeiwen.com/i5015685/853765857dc8e999.png)
如前面所述,Global Dispatch Queue并行执行多个追加任务,而Serial Dispatch Queue 同时只能执行一个追加任务。虽然这两种队列受到系统资源的限制,但是用dispatch_queue_create API 可以生成任意多个Dispatch Queue。但是在指明生成类型为Concurrent类型也就是Global dispatch queue类型时。需要指明创建队列的优先级类型。下文会详细讲解优先级。
通过此API创建的queue存在的最大问题是,尽管有ARC这一通过编译器自动管理内存的优秀技术,但是通过dispatch_queue_create API生成的queue必须由程序员手动释放,这是因为dispatch queue并没有想Block那样具有Object -C对象处理的技术,借此可以通过dispatch_release来释放
dispatch_retain(myCocurrentDispatchQuene);
dispatch_release(myserialDispatchQuene)
对该名称中含有release,相应的就能推断出一定具有retain方法。
dispatch_retain(myCocurrentDispatchQuene);
即dispatch queue和OC对象一样,需要通过dispatch_retain 函数和dispatch_release 函数的引用计数来管理内存。
![](https://img.haomeiwen.com/i5015685/d9a6b9b50f02a991.png)
特别注意。 能够使用dispatch_retain 和 dispatch_release 的地方不仅仅是dispatch queue中,在之后介绍的通过”create“字段创建的API在不需要的时候都有必要通过dispatch_release 函数进行释放,否则会导致内存泄漏。也就是说有create出现的API ,随后就应该有相应的release,它两会成对出现,如果有使用retain增加引用,则多调用一次release将其创建的任务销毁并释放。
3、 Main Dispatch Queue / Global Dispatch Queue
Main Dispatch Queue 正如其中的”Main“一样,实在主线程中执行,因为主线程只有一个,所以Main Dispatch Queue也就是Serial Dispatch Queue,这正好和performSelectorOnMainThread方法是一样的。另一个Global Dispatch Queue 有四个优先级,分别是高、默认、低、后台。
![](https://img.haomeiwen.com/i5015685/52307e6592c75da4.png)
获取队列的方式
![](https://img.haomeiwen.com/i5015685/27de1d52b279db5e.png)
![](https://img.haomeiwen.com/i5015685/fe45d6012b927cd9.png)
另外,Main Dispatch Queue 和Global Dispatch Queue 执行dispatch_retain 和dispatch_release函数不会引起任何变化。也不会有任何问题。这也是获取队列比用create API 创建显得简洁,轻松等原因,因为这样系统已经对内存管理进行了处理,并不需要我们去手动释放和销毁。
4、dispatch_set_target_queue
通过dispatch_queue_create 生成的queue不管是Serial Dispatch Queue 还是Concurrent Dispatch Queue,都使用默认优先级Global Dispatch Queue相同执行优先级的线程,而变更优先级就要使用dispatch_set_target_queue,在后台执行动作处理的serial Dispatch Queue生成方法如下:
![](https://img.haomeiwen.com/i5015685/5317011abe3e96d6.png)
在必须将不可并行执行的处理追加到多个serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个serial Dispatch Queue 即可以防止处理并行执行。
5、dispatch_after
在iOS开发过程中,经常会有想等某个任务执行完再去执行别的事情,或者说是延迟一段时间再去做接下来的事情,这样就了延迟执行的概念,像这种想在指定时间后执行处理的情况就可以使用dispatch_after函数来实现。
![](https://img.haomeiwen.com/i5015685/a76c853e9cf3a38e.png)
注意:dispatch_after 函数并不是在指定时间后执行处理,而只是在指定时间后追加处理到Dispatch Queue,因为Main Dispatch Queue 在主线程的Runloop执行,比如每个1/60秒执行Runloop中。Block最快也要3秒后用dispatch_async函数追加Block到Main Queue中。如果这时候Main Dispatch Queue 中有大量处理或者本身就存在延迟,这个时间会更长。
第一个参数是dispatch_time_t类型,有两种时间类型,dispatch_time和dispatch_walltime,dispatch_time 是计算相对时间,相对的起点可以由自己去设置,可以现在,将来,过去。dispatch_walltime 是计算绝对时间,比如2011年11月11日几分几秒的时间就由这个计算。
6、Dispatch Group
在追加到Dispatch Queue中的多个处理全部处理完成后想执行结束处理,这种情况经常存在,只使用一个serial Dispatch Queue,只要再想执行的处理之后不断追加结束处理就可以实现。但是在Concurrent Dispatch Queue 中或同事追加多个Dispatch Queue,就会非常复杂,
![](https://img.haomeiwen.com/i5015685/dbec3bda6d5d2022.png)
dispatch queue
![](https://img.haomeiwen.com/i5015685/df70c7e22cd1caec.png)
无论向什么类型的Dispatch Queue 中追加处理,使用Dispatch Group都能监视这里处理的结束,一旦所以的处理都执行完毕,就会执行dispatch_group_notify函数,告知系统所以的处理都要执行完毕。
另外,如果不适用dispatch_group_notify这个方法来监视也可以,还有别的方法能达到相同的效果,那就是 dispatch_group_wait方法也能达到这样监听的作用,如果 dispatch_group_wait返回值是0,那么久说明所有的处理都执行完毕了,
![](https://img.haomeiwen.com/i5015685/12e238505b0d1fda.png)
注解,dispatch_group_wait第二个参数为dispatch_time_t类型,意思就是制定等待的时间,如果指定是具体的数,那就是指定等待的秒数,比如设置为1,那么就意味着等待的时间是1秒,如果设置为DISPATCH_TIME_FOREVER,意味着永久等待,中途不能取消。此处还是推荐使用dispatch_group_notify方法来监视,因为使用dispatch_group_notify会优化代码,减少相关的判断。
7、dispatch_barrier_async
dispatch_barrier_async 俗称栅栏函数,其作用是保证追加在它之前的任务执行完了过后吗,在将栅栏函数的处理任务追加到Dispatch Queue中,追加完了过后继续并行执行后边的任务,起到终端的左右,保证在某个任务执行完成后去执行别的任务。
![](https://img.haomeiwen.com/i5015685/32e6d965f4573e5e.png)
在此处的作用就是在blk0,blk1,blk2,blk3,进行读数据完成后,将blk_for_writing的数据写入,待写入完成后,blk4,blk5在进行相关的读数据操作
8、dispatch_sync
之前一直在讲的都是dispatch_async,意思是异步执行,顾名思义,dispatch_sync就是同步执行,也就是将指定BLock同步追加到指定的dispatch queue中,在追加结束后,dispatch_sync会一直等待。
dispatch_sync(queue, ^{
//当追加到队列中就一直等待,
NSLog(@"你好");
});
一旦调用dispatch_sync函数,那么在指定的处理结束之前,该函数不会返回,dispatch_sync可以简化相关的代码,甚至可以说是dispatch_group_wait的简易版。正因为dispatch_sync 使用简单,所以也容易因为死锁问题。
//这个方法容易引起死锁
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_barrier_sync(main, ^{
dispatch_sync(queue, ^{
NSLog(@"hello");
});
});
这个代码在程序中会出现闪退问题。而用global dispatch queue 则不会,所以希望在开发中使用同步还是异步,考虑清楚了再用,减少给自己带来的不必要的麻烦。
9、dispatch_apply
dispatch_apply 函数是dispatch_sync函数和dispatch_group函数的关联,该函数的作用是按指定的次数将指定的Block追加到指定的dispatch queue中。并等待全部执行处理完成。
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dd
dispatch_apply(10, defaultQueue, ^(size_t index) {
NSLog(@" %zu",index);
});
![](https://img.haomeiwen.com/i5015685/d3cc79501f215c23.png)
可以利用这个函数来执行数组循环,来对数组进行相关的操作
NSMutableArray *array = [[NSMutableArray alloc]init];
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], defaultQueue, ^(size_t index) {
// arry do something;
});
10、dispatch_suspend / dispatch_resume
当追加大量处理到dispatch queue时,在追加处理的过程中,有时候希望不执行已经追加的处理,在这种情况下,只要挂起dispatch queue即可,当可以执行的时候再恢复,
dispatch_suspend(queue);
dispatch_resume(queue);
11、Dispatch Semaphore
当并行执行的处理更新数据时,会产生数据不一致的情况,有时候程序还会异常的结束,虽然是同serial dispatch queue 和dispatch_barrier_async函数可避免这类情况,但是有必要进行更细粒度的排他控制。例如
NSMutableArray *array = [[NSMutableArray alloc]init];
for (int i = 0; i< 10000; i++) {
dispatch_async(defaultQueue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}
因为该源代码用global dispatch queue 更新NSMutableArray类对象。所以执行后由内存错误导致应用程序异常的概率很高,此时就应该用Dispatch Semaphore。
Dispatch Semaphore是多线程中持有计数的信号,相当于过马路的时候交警指挥交通的红旗,举起红旗通过,放下红旗表示不能通过,在 Dispatch Semaphore中,使用计数来实现该功能,计数0时等待,计数为1或者大于1时,减去1不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
从函数名称可以看出。该函数与dispatch queue 和dispatch group 一样,必须通过dispatch release 进行释放,当然也可以用dispatch retain持有,
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大于或者等于1,当计数值达到大于或者等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。第二个参数也是表示等待的时间,表示永久等待。返回值也和dispatch_group_wait一样
![](https://img.haomeiwen.com/i5015685/d8fcb500a3aa110f.png)
当返回值为0 的时候,可安全的执行需要的排他处理,改处理结束后通过dispatch_semaphore_signal函数将dispatch semaphore的计数加1,用Dispatch Semaphore 对处理NSMutableArray类对象的修复
![](https://img.haomeiwen.com/i5015685/c8e82f551e9c1a64.png)
在没有serial Dispatch Queue 和 dispatch_barrier_async函数那么大粒度且一部分需要进行排他控制,选择用Dispatch Semaphore 是最好的选择。
12、dispatch_once
dispatch_once 函数是保证在应用程序中只执行一次指定处理的API,特别是在项目中使用单例的情况下建议使用dispatch_once
![](https://img.haomeiwen.com/i5015685/a34bd817b4f06fd4.png)
两个源代码看起来没有太大的变化,但是通过dispatch_once 函数,该源代码即使在多线程环境下执行,也会百分百的安全,这就是dispatch_once 的好处。
13、Dispatch I/O
通过Dispatch I/O读写文件时,可以使用Global Dispatch queue 将一个较大文件按照固定的大小片段读写 例如。
![](https://img.haomeiwen.com/i5015685/3e51db7d00c295de.png)
如上显示,将文件分成一块一块的进行读写处理,分割读取的数据通过使用Dispatch Data 可更为简单的进行数据结合和分割。
三、总结
以上内容是本人对多线程学习过程中的个人认知,仅限个人的感悟,不一定完全正确,如果绝对我不对的地方,欢迎指正错误,本人一定虚心改正错误,向大神学习,谢谢。
网友评论