前言:这篇GCD的博文是本人阅读了很多海内外大神的关于GCD的文章,以及结合之前自己对GCD的粗浅的认识,然后取其精华,去其槽粕,综合起来的笔记,而且是尽可能的以通熟易懂的并且是正确的理论论述方式呈现给读者,同时也是讲大神博客中有的深涩的理论理解的通熟易懂转述给读者,已经是尽可能的让读者深入理解和掌握多线程的知识以及GCD的使用技术。最后的附录中,我将会给出所有本人阅读的大神写的关于多线程或者是GCD的文章链接,大家感兴趣的,可以去参考和学习。也许,看我的这篇就够了,因为我就是参考他们的,嘻嘻。??
本篇博文:持续更新,还未更新完。
内容大纲:
相关基本概念
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学习的网页链接
------------------------------------------
另外呢,本人对GCD常用的API接口进行了简易封装,用起来很简单,代码可读性大大提高了,github网址是:https://github.com/HeYang123456789/HYGCD。
如果接的好,记得在github上点个赞呀。
好了,我就不为自己打广告了。下面请读者认真学习下面的内容吧。
相关基本概念
1、什么是GCD?
iOS实现提供实现多线程的方案有:NSThread、NSOperation、NSInvocationOperation、pthread、GCD。
在iOS所有实现多线程的方案中,GCD应该是最有魅力的,而且使用起来也是最方便的,因为GCD是苹果公司为多核的并行运算提出的解决方案。
GCD是Grand Central Dispatch的简称,它是基于C语言的。使用GCD,我们不需要编写线程代码,其生命周期也不需要我们手动管理,定义想要执行的任务,然后添加到适当的调度队列,也就是dispatch queue。GCD会负责创建线程和调度任务,系统直接提供线程管理。
2、任务(Task)
一段代码块,执行之后能够实现的一段业务逻辑过程。一般用闭包或者叫block,来包含它。执行任务,就需要将这个闭包或者block交给队列,然后让队列 先进先出(FIFO) 的顺序取出来,放进线程中执行。
3、队列(Queue)
我们需要了解队列的概念,GCD提供了dispatch queues来处理代码块,这些队列管理所提供给GCD的任务并用FIFO顺序执行这些任务。这样才能保证第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。
串行(Serial)队列
串行队列 中的任务会根据队列定义 先进先出(FIFO) 的顺序取出来一个,执行一个,执行完了然后取出下一个任务,这样一个一个的执行。
串行队列图:
并发(Concurrent)队列
并发队列 中的任务也会根据队列定义 先进先出(FIFO)的顺序一个个取出来,但是与串行队列不同的是,并发队列取出一个任务会放到别的线程中开始执行,先不管这个线程是同步线程还是异步线程,并发队列并不是等线程中的任务执行完了而是取完了这个任务,就会立刻接着从并发队列中取下一个任务放到别的线程中开始执行。由于取任务的速度很快,忽略不计,看起来,好像所有任务都是同时开始执行任务的。所以叫"并发队列"。但是,任务有长有短,任务执行结束的顺序你是无法确定的,有可能那个最短的任务先执行完毕了。
并发队列图:
补充:并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉,如下图所示:
4、队列的类型(Queue Types)
系统提供的:
一个主队列(main queue),是串行队列。
和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
在使用主线程串行队列时,我们只通过dispatch_get_main_queue方法来获取即可,我们不需要管理其内存问题。
四个全局调度队列(Global Dispatch Queues),都是并行队列
四个全局队列有着不同的优先级:background、low、default 以及 high。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
在使用全局并发队列时,我们只通过dispatch_get_global_queue方法来获取即可,我们不需要管理其引用。
你也可以创建自己的串行队列或并发队列。
总结:这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。
以上是调度队列的大框架!
5、线程
线程分为两种:Synchronous vs. Asynchronous 同步 vs. 异步。
GCD通过调用以下两个函数分别创建同步任务和异步任务,放置队列中,最后从队列中取出交由线程执行:
这里会有两个东西要说:同步函数和异步函数。我们的C语言基础就学了函数,函数用大括号包含了一段代码块,当这个函数执行完毕就会返回,可以是返回void或者返回具体某个数据值,同时也代表着这个函数的执行完毕。那么线程中的函数,当然是包含了任务的,这个任务就是闭包或者叫block。同步函数和异步函数的区别就在,同步函数需要在完成了它预定的任务后才返回,而异步函数会立即返回,也就是说异步函数预定的任务会完成但不会等它完成就立即返回。
另外,同步线程是不具备创建子线程的能力的,一般情况下,同步线程的任务会在唯一的主线程中执行,也就是说唯一的(main)主线程就成了同步线程。而异步线程是具备创建子线程的能力的,一般情况下,异步线程就是(main)主线程以外的线程,不唯一,会有多个,具体多少个,具体是哪些线程,如果是通过GCD创建,全部由GCD决定。关于能创建的子线程的最大个数,这个就需要根据设备的CPU的能力了,比如下面是本人用真机,通过使用GCD创建了多个异步任务,并添加进并发队列执行测试的结果:
可以发现,我创建了8个异步任务,GCD根据设备的极限,创建了三个子线程,分别是线程2、3、4,然后由它们随机执行任务。其中线程4执行了四个任务,线程2和线程3分别执行了两个任务。
那么,同步还是异步,这两个术语是需要多个函数任务之间对比来论述的,这样会更好理解。比如说同步,当线程执行一个同步函数任务的时候,这个线程会停下来,也就是所谓的"当前这个线程阻塞"了,当前线程需要等待这个函数执行完毕返回了值,也表示这个函数中的任务完全执行完毕了,这个线程才会继续执行下去,去执行下一个函数任务,所以这样的线程也叫同步线程。而异步函数所在的线程就不一样了,线程开始执行一个异步函数任务的时候,因为异步函数立即返回了,虽然这个函数任务可能还没执行完毕,但是返回了,这个线程就会继续执行下一个函数任务,由于这个过程很快,快到可以忽略不计任务执行开始的先后顺序,快到感觉好像都是同时开始执行任务的,所谓的"异步线程同时执行多个任务"就是这么来的。
注意:
不要将耗时操作放在主线程中,凡是跟UI相关的操作都是放在主线中处理
耗时操作应该放在子线程(后台线程,非主线程)
6、总结一下同时使用队列和线程
好,下面我们来看看这个图,可能会和你们别处看的有点不一样,这是本人根据上面理论进行了适当的修改:
6-1、串行队列+同步:
假设一堆任务放在串行队列中。
因为串行队列是先进先出FIFO队列,而且串行队列是需要前面取出的一个任务执行完了之后,才接着取下一任务。
因为同步(sync)没有开启新线程的能力,同步函数会阻塞当前线程。
所以,这一堆任务,将会在串行队列和main主线程中,
一个任务从串行队列提交到这个主线程,在这个任务执行完毕了之后,才接着从串行队列中取出下一个任务提交到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、并发队列+同步:
假设一堆任务放在并发队列中。
因为并发队列也是先进先出FIFO队列,但是并发队列是需要取出的一个任务,这个任务还没执行完了,就立刻接着取下一任务。
虽然并发队列也是先进先出FIFO队列,但是取出的速度很快,可以忽略不计,就好像是同时取出所有的任务,因此叫"并发队列"。
因为同步(sync)不具有开启新线程的能力,同步函数会阻塞当前线程。
所以,这一堆任务,将会在并发队列和main主线程中,
所有的任务会快速的提交到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、串行队列+异步:
假设一堆任务放在串行队列中。
因为串行队列是先进先出FIFO队列,而且串行队列是需要前面取出的一个任务执行完了之后,才接着取下一任务。
因为异步(async)有开启新线程的能力,异步函数不会阻塞当前线程。
所以,这一堆任务,将会在串行队列和开启的新线程中,
一个任务从串行队列提交到这个主线程,在这个任务执行完毕了之后,才接着从串行队列中取出任务提交到新线程中,就这样按顺序执行任务。
验证代码:
打印结果:
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、并发队列+异步:
假设一堆任务放在并发队列中。
因为并发队列也是先进先出FIFO队列,但是并发队列是需要取出的一个任务,还没执行完了,就立刻接着取下一任务。
虽然并发队列也是先进先出FIFO队列,但是取出的速度很快,可以忽略不计,就好像是同时取出所有的任务,因此叫"并发队列"。
因为异步(async)具有开启新线程的能力,异步函数不会阻塞当前线程。
所以,这一堆任务,将会在并发队列和新线程中,
所有的任务会快速的提交到新线程上,如果当前新线程可能一时忙碌,并发队列就可能把任务交到另一个新的线程中,就这样多条线程的同时又是异步快速的执行任务。
验证代码:
打印结果:
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秒之后同时执行,队列快速提交任务到各个线程,各个线程没有阻塞任务。
GCD技术浅谈
1、延迟
方法1:使用NSObject的api,延迟同步执行(不是延迟提交):
[self performSelector:@selector(myFunction) withObject:nil afterDelay:5.0];
补充:这个有延时执行取消的操作:
[NSObject cancelPreviousPerformRequestsWithTarget:self];//后面的参数就是执行performSelector的对象,在这里是self
方法2:使用NSTimer定时器(不是延迟提交)。
略。(补充,NSTimer有一个方法可以定时器精确到0.01秒,也就是每0.01秒可以打印出一条语句,当然这是没有考虑屏幕刷新速率的)
方法3:使用dispatch_after方法异步延迟执行(是延迟提交):
CGFloat time = 5.0f;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// time秒后异步执行这里的代码...
});
补充:dispatch_after是延迟提交,不是延迟执行。
官方说明:
Enqueue a block for execution at the specified time.
Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!。
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 不等于 "立即停止队列的运行"
dispatch_suspend,dispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。
但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,看如下例子:
打印结果:
本质:
suspend就是暂停取出队列中的任务,暂停之前已经从队列中取出的任务自然就让它执行完为止了。
如果是并发队列,然后暂停稍微晚一点,全部的任务都可能已经从队列取出提交到线程中执行了。读者可以自己敲代码验证。
4、避免死锁
什么是死锁?
其实就是任务互相等待。
下面看起来简单,但是会出现死锁哦,如果你的app出现了下面的情况,就会卡死不动的哦:
1、sync函数互相嵌套,产生死锁
这个很好理解,updateUI1中的同步任务肯定比updateUI2先执行,因为只有updateUI1的任务执行完毕,updateUI2的任务被创建添加到线程中去并执行。
所以,首先我们可以确定updateUI1先执行,然后updateUI2后执行。
但是,因为updateUI1包含updateUI2作为任务,所以updateUI2执行完毕了,这样updateUI1就算执行完毕了。如果updateUI2没执行完,updateUI1就没执行完毕。
可是,根据同步函数会阻塞的缘故,updateUI2需要等待updateUI1执行完毕了,才会轮到updateUI2执行。
就这样,互相等待了。于是就出现死锁了。
2、在main线程使用“同步”方法提交Block,必定会死锁。
那么这个和前面一个情况是一样的。其实程序逻辑就在当前运行的线程中执行,也就是主线程,主线程上的任务函数就是同步函数。
因为,你写的一段一段代码可不允许并发执行。如果并发执行的话,那你的代码逻辑还不混乱了,你都不知道那段代码先执行了,那段代码后执行了。
也许你可能看不出来,那你可以直接加一句NSLog(@"当前线程是:%@",[NSThread current]);,由此可以证明当前执行的代码就是在main线程中。
然后,你有在当前main线程中,创建一个在main线程中的同步函数任务。
这样就和第一种情况一样了。就出现死锁了。
所以,尽量少用sync吧。
5、GCD信号量
关于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(@".......完成、、、、、、");//这个会先于里面的打印
关于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]);
});
如果前面理解了信号量,那么这段代码就很好理解了。
关于信号量的使用易错点补充:
下面是本人出错的时候的代码:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
dispatch_async(queue, ^{
// +1
dispatch_semaphore_signal(semaphore);
NSLog(@"Hello - 2");
});
// 等待 -1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Hello - 1");
});
}
错误的部分:红色的代码应该放在NSLog(@"Hello - 2")之后的,按照上面错误的逻辑就是这样的:当代码执行到红色的代码的时候,
因为信号量是全局管理所有线程的,那么红色代码就是发送信号量+1,这样型号两综合就不等于-1了,就这样所有线程都开通不停滞了。
接着上面代码的两个任务"NSLog(@"Hello - 2");"和"NSLog(@"Hello - 1");"又开始进入并发执行了,所以又进入随机的执行
顺序了。
为了保证内部的线程任务先执行,就必须把红色代码,也就是发送信号量的代码放在当前任务函数的最后一行,保证前面的任务先执行。
6、GCD的计时器
代码示例:
计时器事件稍有不同。它们不使用handle/mask参数,计时器事件使用另外一个函数 dispatch_source_set_timer
来配置计时器。这个函数使用三个参数来控制计时器触发:
start
参数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t
,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time
和 dispatch_walltime
函数来创建它们。另外,常量 DISPATCH_TIME_NOW
和 DISPATCH_TIME_FOREVER
通常很有用。
interval(n. 间隔;间距;幕间休息)
参数没什么好解释的。
leeway
参 数比较有意思。这个参数告诉系统我们需要计时器触发的精准程度。所有的计时器都不会保证100%精准,这个参数用来告诉系统你希望系统保证精准的努力程 度。如果你希望一个计时器没五秒触发一次,并且越准越好,那么你传递0为参数。另外,如果是一个周期性任务,比如检查email,那么你会希望每十分钟检 查一次,但是不用那么精准。所以你可以传入60,告诉系统60秒的误差是可接受的。
这样有什么意义呢?简单来说,就是降低资源消耗。如果系 统可以让cpu休息足够长的时间,并在每次醒来的时候执行一个任务集合,而不是不断的醒来睡去以执行任务,那么系统会更高效。如果传入一个比较大的 leeway给你的计时器,意味着你允许系统拖延你的计时器来将计时器任务与其他任务联合起来一起执行。
转载本文注明出处:http://www.cnblogs.com/goodboy-heyang/p/5271513.html
附录:很好的GCD学习的网页链接
1、GCD这块已经开源,地址http://libdispatch.macosforge.org
2、唐巧的技术博客:《使用GCD》,地址:http://blog.devtang.com/2012/02/22/use-gcd/
3、大神翻译自国外IOS很不错的学习网站文章《Grand Central Dispatch In-Depth: Part 1/2》:https://github.com/nixzhu/dev-blog
4、标哥的技术博客:《GCD由浅入深学习》:http://www.henishuo.com/gcd-multiple-thread-learn/
5、土土哥的《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
7、《关于IOS多线程,你看我就够了》:http://www.jianshu.com/p/0b0d9b1f1f19
8、《细说GCD(Grand Central Dispatch)如何使用》 :http://www.jianshu.com/p/fbe6a654604c
9、《【iOS】GCD死锁》;http://www.brighttj.com/ios/ios-gcd-deadlock.html
10、很高大上的一个牛逼网站:http://www.dreamingwish.com/article/gcdgrand-central-dispatch-jiao-cheng.html