1.内容大纲:
相关基本概念
1、什么是GCD?
2、任务(Task)
3、队列(Queue)
4、队列的类型(Queue Types)
5、线程
6、总结一下同时使用队列和线程
GCD技术浅谈
1、延迟
2、正确创建dispatch_time_t
3、dispatch_suspend 不等于 "立即停止队列的运行"
4、避免死锁
5、GCD信号量
6、GCD的计时器
另外呢,本人对GCD常用的API接口进行了简易封装,用起来很简单,代码可读性大大提高了,github网址是:https://github.com/HeYang123456789/HYGCD。
2.GCD相关概念:
1.什么是GCD:
1.iOS实现提供实现多线程的方案有:NSThread、NSOperation、NSInvocationOperation、pthread、GCD。
2.在iOS所有实现多线程的方案中,GCD应该是最有魅力的,而且使用起来也是最方便的,因为GCD是苹果公司为多核的并行运算提出的解决方案。
3.GCD是Grand Central Dispatch的简称,它是基于C语言的。使用GCD,我们不需要编写线程代码,其生命周期也不需要我们手动管理,
定义想要执行的任务,然后添加到适当的调度队列,也就是dispatch queue。GCD会负责创建线程和调度任务,系统直接提供线程管理。
2.任务(Task):
1.一段代码块,执行之后能够实现的一段业务逻辑过程。一般用闭包或者叫block,来包含它。
执行任务,就需要将这个闭包或者block交给队列,然后让队列 先进先出(FIFO) 的顺序取出来,放进线程中执行。
3.队列(Queue):
1.我们需要了解队列的概念,GCD提供了dispatch queues来处理代码块,这些队列管理所提供给GCD的任务并用FIFO顺序执行这些任务。
这样才能保证第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。
2.串行队列(Serial)
串行队列中任务根据队列定义,先进先出[FIFO]的顺序一个一个的取出来了,执行一个完了然后在取出下一个任务,这样一个一个的执行!
3.并发队列(Concurrent):
并发队列 中的任务也会根据队列定义 先进先出(FIFO)的顺序一个个取出来,
但是与串行队列不同的是,并发队列取出一个任务会放到别的线程中开始执行,
先不管这个线程是同步线程还是异步线程,并发队列并不是等线程中的任务执行完了而是取完了这个任务,就会立刻接着从并发队列中取下一个任务放到别的线程中开始执行。
由于取任务的速度很快,忽略不计,看起来,好像所有任务都是同时开始执行任务的。所以叫"并发队列"。
但是,任务有长有短,任务执行结束的顺序你是无法确定的,有可能那个最短的任务先执行完毕了。
串行队列图:
并发队列图:
4.队列的类型(Queue Types):
1.一个主队列(main queue),是串行队列。
特点:
和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
dispatch_get_main_queue(主队列,主线程中的唯一队列,一个串行队列)
2.全局调度队列(Global Dispatch Queues),都是并发队列。
特点:
全局队列有着不同的优先级:background、low、default 以及 high。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
3.手动创建队列:
<1>串行队列: dispatch_queue_create("zhufeng",DISPATCH_QUEUE_SERIAL)
<2>并发队列:
dispatch_queue_create("zhufeng",DISPACHT_QUEUE_CONCURRENT)
总结:
有五个队列任你处置:主队列,四个全局队列,加上自己创建的队列!!!
主队列图:
四个全局队列:
串行队列和并发队列:
5.线程的两种方式:
线程分为两种:Synchronous vs. Asynchronous 同步 vs. 异步。
GCD通过调用以下两个函数分别创建同步任务和异步任务,放置队列中,最后从队列中取出交由线程执行:
1.同步函数和异步函数的区别:
1.同步函数和异步函数的区别就在,
同步函数需要在完成了它预定的任务后才返回,而异步函数会立即返回,
也就是说异步函数预定的任务会完成但不会等它完成就立即返回。
2.同步函数布局创建子线程的能力,同步函数会在唯一的主线程中执行,
异步函数具备子线程的能力,除main主线程以外的线程,不唯一,会有多个
根据CPU的性能决定的,关于创建的子线程的最大个数!!
3.当前线程需要等待这个函数执行完毕返回了值,也表示这个函数中的任务完全执行完毕了,
这个线程才会继续执行下去,去执行下一个函数任务,所以这样的线程也叫同步线程。
而异步函数所在的线程就不一样了,线程开始执行一个异步函数任务的时候,因为异步函数立即返回了,
虽然这个函数任务可能还没执行完毕,但是返回了,这个线程就会继续执行下一个函数任务,由于这个过程很快,快到可以忽略不计任务执
异步函数创建多个的线程的示意图:
2.注意点:
1.不要将耗时操作放在主线程中,凡是和UI相关的操作都是放在主线程去了
2.耗时操作应该放在子线程(后台线程,非主线程)
6.总结同时使用队列和线程:
6.1 串行队列+同步函数
1.假设一堆任务放在串行队列中。
2.因为串行队列是先进先出FIFO,并且串行队列需要的是前面取出的一个任务执行完了之后,才会接着取下一个任务。
3.因为同步(sync)没有开启新线程的能力,同步函数就会阻塞当前线程.
所以,这一堆任务,将会在串行队列和main队列中,
4.一个任务串行队列提交到主线程,在这个任务执行完了之后,才接着从串行队列取出下一个任务提交到main主线程中,就这样按照顺序执行任务..
验证代码:
打印结果:
2016-03-13 15:51:04.970 多线程[25106:632356] 当前线程是:<NSThread: 0x7fe28bc025b0>{number = 1, name = main}
2016-03-13 15:51:06.974 多线程[25106:632356] 当前线程是:<NSThread: 0x7fe28bc025b0>{number = 1, name = main}
2016-03-13 15:51:08.979 多线程[25106:632356] 当前线程是:<NSThread: 0x7fe28bc025b0>{number = 1, name = main}
2016-03-13 15:51:10.980 多线程[25106:632356] 当前线程是:<NSThread: 0x7fe28bc025b0>{number = 1, name = main}
分析打印结果:每隔2秒执行了一个任务,说明出现了阻塞,执行的线程是main主线程.
6.2 并发队列+同步函数:
1.假设一堆任务放在并发队列中,
2.因为并发队列也是先进先出FIFO队列,但是并发队列的是需要取出一个任务,这个任务还没执行完了,就立刻接着下一个任务.
3.虽然并发队列也是先进先出FIFO队列,但是取出的速度很快,可以忽略不计,就好像同时取出所有的任务,因此叫“并发队列”.
4.因为同步”sync“不具有开启新线程的能力,同步函数会阻塞当前线程。
所以,这个一堆任务,将会在并发队列和main主线程中
5.所有的任务会快速的提交到main主线程上,一个任务执行完毕之后接着开始执行下一个任务,就这样按顺序任务
验证代码:
打印结果:
2016-03-13 16:02:51.566 多线程[25327:637330] 当前线程是:<NSThread: 0x7fe53b6039c0>{number = 1, name = main}
2016-03-13 16:02:53.570 多线程[25327:637330] 当前线程是:<NSThread: 0x7fe53b6039c0>{number = 1, name = main}
2016-03-13 16:02:55.575 多线程[25327:637330] 当前线程是:<NSThread: 0x7fe53b6039c0>{number = 1, name = main}
2016-03-13 16:02:57.577 多线程[25327:637330] 当前线程是:<NSThread: 0x7fe53b6039c0>{number = 1, name = main}
分析打印结果:每隔2秒执行一个任务,说明出现了阻塞,执行的线程是main线程
6.3 串行队列+异步函数:
1.假设一堆任务放在串行队列中。
2.因为串行队列是先进先出FIFO队列,而且串行队列是需要取出的一个任务执行挖了之后,才接着取出下一个任务。
3.因为异步 (async)有开启线程的能力,异步函数是不会阻塞当前线程
所以这个任务,将会在串行队列和开启的新线程中去执行。
4.一个任务从串行队列提交到这个主线程,在这个任务执行完毕了之后,才接着从串行取出任务提交到新的线程中,就这样按照顺序执行任务.
验证代码:
打印结果:
2016-03-13 16:09:23.744 多线程[25460:640764] 当前线程是:<NSThread: 0x7fb08b638e00>{number = 2, name = (null)}
2016-03-13 16:09:25.750 多线程[25460:640764] 当前线程是:<NSThread: 0x7fb08b638e00>{number = 2, name = (null)}
2016-03-13 16:09:27.753 多线程[25460:640764] 当前线程是:<NSThread: 0x7fb08b638e00>{number = 2, name = (null)}
2016-03-13 16:09:29.757 多线程[25460:640764] 当前线程是:<NSThread: 0x7fb08b638e00>{number = 2, name = (null)}
分析打印结果:每隔2秒执行一个任务,说明串行队列出现了等待一个取出的任务执行完毕,执行的线程是新线程。
6.4并发队列+异步函数:
1.假设一堆任务放在并发队列中。
2.因为并发队列也是先进先出FIFO队列,但是并发队列是需要取出的一个任务,还没执行完了,就立刻接着取下一个任务.
3.虽然并发队列也是先进先出FIFO队列,但是取出的速度很快,可以忽略不计,就好像是同时取出所有的任务,因此叫做“并发队列”。
4.因为异步“async”具有开启新线程的能力,异步函数不会阻塞线程。
所以,这个一堆任务,将会在并发队列和新的线程中。
5.所有的任务快速的提交到新线程上,如果当前新线程可能一时忙碌,并发对了可以把任务提交到另外一个新线程中,就这样多条线程的同时又是异步开始的加载任务
验证代码:
打印结果:
2016-03-13 16:17:24.685 多线程[25648:645355] 当前线程是:<NSThread: 0x7f93a0605b70>{number = 1, name = main}
2016-03-13 16:17:28.693 多线程[25648:645477] 当前线程是:<NSThread: 0x7f93a0611a40>{number = 3, name = (null)}
2016-03-13 16:17:28.693 多线程[25648:645480] 当前线程是:<NSThread: 0x7f93a0620220>{number = 5, name = (null)}
2016-03-13 16:17:28.693 多线程[25648:645476] 当前线程是:<NSThread: 0x7f93a0706f60>{number = 2, name = (null)}
2016-03-13 16:17:28.693 多线程[25648:645478] 当前线程是:<NSThread: 0x7f93a0409ee0>{number = 4, name = (null)}
分析打印结果:主线程和新线程都休眠2秒,所以子线程的任务在4秒之后同时执行,队列快速提交到各个线程,各个线程没有阻塞任务.
3.GCD技术浅谈:
1.延迟方法:
3.1.方式一:使用NSObject的api,延迟同步执行(不是延迟操作)
[self perfrormSelector:@selector(mufunction) withObjec:nil afterDelay:0.5];
补充:这个有延时执行取消的操作
[NSObject cancelPreviousPerfromRequestWithTarget:self]//后面的参数就是执行perfromSeletor的对象,这里是self对象
3.2方式二: 使用NSTimer定时器(不是延迟提交)
3.3方式三:dispatch_after方法异步延迟操作(是延迟提交)
CGFloat time = 0.5f;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(time *NSEC_PER_SEC)),dispatch_getn_main_queue(),^{
//time秒之后异步执行的这里的代码了
})
补充:dispatch_after是延迟提交,不是延迟执行
2.正确的创建dispatch_time_t
用dispatch_after的时候就会用到dispatch_time_t变量,但是如何创建合适的时间呢?答案就是用dispatch_time函数,其原型如下:
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。
第二个参数就是真正的延时的具体时间。
这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000 000 000”,太长了,所以理所当然系统提供了常量,如下
关键词解释:
NSEC:纳秒。
USEC:微妙。
SEC:秒
PER:每
所以:
NSEC_PER_SEC,每秒有多少纳秒。
USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
NSEC_PER_USEC,每毫秒有多少纳秒。
注意dispatch_time_t的第二个参数是纳秒为单位的。所以当你第二个参数传入1000 000 000ull或者是NSEC_PER_SEC就表示1秒了。
3.dispatch_suspend 不等于 “立即停止队列的运行”
1.dispatch_suspend,dispatch_resume提供了“挂起”,“恢复”队列的功能,简单来说,可以是暂停,恢复队列的任务,
但是这里的“挂起”,并不能保证可以立即停止队列的正在运行的blcok,看下面列子:省略
2.本质:
suspend就是暂停取出队列中的任务,暂停之前一次从队列中取出的任务自然就让他执行完了为止,
如果是并发队列,然后暂停会稍微晚一点,全部的任务都可以已经从队列取出提交到线程中执行去了,自己写代码验证
4.避免死锁:
1.什么是死锁:就是任务互相的等待,
2.下面看起啦很简单,但是会出现死锁的问题,如果你的app出现了下面的情况,就会卡死不动的哦:
1.sync函数的互相嵌套,产生死锁!
2.在main线程中使用”同步“方式提交block,必定死锁
那么这个和前面一个情况一样的,其实就是程序逻辑就在当前的线程中执行,也就是主线程,主线程任务的函数又是同步函数
总结:
在我们的实际的开发中,尽量的少用sync同步函数,使用这个会出现死锁
5.GCD信号量:
1.GCD信号量实例一:
/**
* 当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore,对经常做unix开发的人来讲,我所介绍的内容可能就显得非常入门级了,信号量在他们的多线程开发中再平常不过了。
信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量减1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制
*/
实现的代码:
//创建一个组
dispatch_group_t group = dispatch_group_create();
//信号初始总量为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
//获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
//信号等待使信号总量-1,开始为10-1=9即继续往下执行
//当循环遍历到10次的时候,这个信号量等待就被执行了10次,第11次到这句信号量等待的代码的时候,信号总量就变成-1,
//当前线程就卡住不会继续执行了,也就是等待的样子了
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//将一个并发任务关联到group
dispatch_group_async(group, queue, ^{
//前面10个异步任务,打印出当前循环的i值
NSLog(@"%i",i);
//打印之后,前面10个异步任务,会停滞休眠2秒
sleep(2);
//停滞休眠2秒之后,10个异步任务同时对信号量加1,
//发送一个信号信号总量+1 如果+1前信号量小于1了即刻又可以开始执行之前的等待位置
dispatch_semaphore_signal(semaphore);
});
}
//等待group相关的所有任务执行完成才往下走
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// dispatch_group_notify(group, queue, ^{
// NSLog(@"完成、、、、、、");//所有group相关执行完的回调
// });
// NSLog(@".......完成、、、、、、");//这个会先于里面的打印
2.GCD信号量示例二:
/**
*
* 下面再展示一个信号量能实现的示例,而且这个示例是来自面试官的问题哦
* 问题需求:两个异步任务嵌套,如何保证在内部的异步任务先执行?
*
* 本人分析:没指明在什么队列中取出任务,我先假设串行队列。
* 如果是串行队列的话,由于串行队列FIFO,先创建的任务肯定先执行,执行完毕之后再执行下一个任务
* 如果是串行队列中有两个异步任务嵌套,那么外部的异步任务一定先执行,因为外部的任务先创建,先进队列的,然后因为队列FIFO
* 总结:如果要两个异步任务嵌套,并且保证在内部的异步任务先执行,那么一定需要并发队列。
要解决面试的问题,本人立马想到使用GCD的信号量的知识,下面是本人立马通过代码实现了解决了这个问题:
*/
代码演示:
// 开始让信号量为0,当信号量小于0的时候,当前线程会停下等待
dispatch_semaphore_t dispatchSemaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("heyang", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
dispatch_async(queue, ^{
sleep(2);
NSLog(@"--1--");
NSLog(@"%@",[NSThread currentThread]);
// 发送一个信号量,就会让那个信号量加1
dispatch_semaphore_signal(dispatchSemaphore);
});
// 信号量等待就会让信号量减1
dispatch_semaphore_wait(dispatchSemaphore, DISPATCH_TIME_FOREVER);
NSLog(@"--2--");
NSLog(@"%@",[NSThread currentThread]);
});
如果前面理解了信号量,那么这段代码就很好理解了。
6.GCD计时器:
代码演示:
附录:很好的GCD 学习的网页连接:
1、GCD这块已经开源,地址[http://libdispatch.macosforge.org](http://libdispatch.macosforge.org/)
2、唐巧的技术博客:《使用GCD》,地址:[http://blog.devtang.com/2012/02/22/use-gcd/](http://blog.devtang.com/2012/02/22/use-gcd/)
3、大神翻译自国外IOS很不错的学习网站文章《[Grand Central Dispatch In-Depth: Part 1/2](https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1)》:[https://github.com/nixzhu/dev-blog](https://github.com/nixzhu/dev-blog)
4、标哥的技术博客:《GCD由浅入深学习》:[http://www.henishuo.com/gcd-multiple-thread-learn/](http://www.henishuo.com/gcd-multiple-thread-learn/)
5、土土哥的《GCD使用经验与技巧浅谈》:[http://tutuge.me/2015/04/03/something-about-gcd/](http://tutuge.me/2015/04/03/something-about-gcd/)
6、YouXianMing老师的《Grand Central Dispatch (GCD) Reference》:[http://www.cnblogs.com/YouXianMing/p/3600763.html](http://www.cnblogs.com/YouXianMing/p/3600763.html)
7、《关于IOS多线程,你看我就够了》:[http://www.jianshu.com/p/0b0d9b1f1f19](http://www.jianshu.com/p/0b0d9b1f1f19)
8、《细说GCD(Grand Central Dispatch)如何使用》 :[http://www.jianshu.com/p/fbe6a654604c](http://www.jianshu.com/p/fbe6a654604c)
9、《【iOS】GCD死锁》;[http://www.brighttj.com/ios/ios-gcd-deadlock.html](http://www.brighttj.com/ios/ios-gcd-deadlock.html)
10、很高大上的一个牛逼网站:[http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html](http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html)