GCD之串行、并行、同步、异步

iOS中关于多线程的技术有NSThread、NSOperation、GCD。目前使用最广泛的是GCD,他自动帮我们管理线程的创建和线程间通信,API接口简单,但是功能强大。

GCD的核心概念如下:串行队列,并行队列,同步任务,异步任务


基本概念:

  • 异步任务在主队列(串行队列)上执行,不会开启新的线程
  • 全局队列是并行队列
  • 主队列是串行队列,主队列上的任务都在主线程中执行
  • dispatch_sync只会在 block 完全执行完之后返回,不会新建线程
  • dispatch_async不能确保会在 block 完全执行完之后返回,唯一能确定的是会在被添加到queue 队列后返回。会新建线程

==============================================

  1. 任务:就是block,同一个block里面的代码一定是按照先后顺序执行,不管一个block里面的两段代码执行期间有多少其他线程执行了任务,但是对于同一个block里面的代码来说,是按照从上到下来执行的。(没有包含其他同步或者异步)
  2. 串行队列:任务按照FIFO的一个个的添加进入线程被执行,必须等到前面一个任务执行完毕,才会接着放入下一个任务放到线程
  3. 并行队列:任务按照FIFO的一个个的添加进入线程,不用等到前面一个任执行完毕,就会接着把下一个任务放到线程中
  4. 异步执行(dispatch_async):重新开新线程,然后把当前的任务添加到新开的线程中执行。
    开多少个新线程根据队列类型决定。

    • 串行队列:重开的线程数为1。
    • 并行队列:重新开启的线程数由系统决定。 在达到系统的最大线程数之前,每执行一个任 务就会重新开一个新的线程,然后把任务添加到新线程中执行。达到最大线程数后,下面的任务都会重用前面开的线程。

    不能确保会在 block 完全执行完之后返回,唯一能确定的是会在被添加到queue 队列后返回

  5. 同步执行(dispatch_sync):在当前线程执行,不另开线程,不管是串行队列还是并行队列。必须等待 block 完全执行完之后才会返回。

==========================

  • 在串行队列中执行同步任务:不会新建线程,按顺序执行任务(毫无用处)
  • 在串行队列中执行异步任务,会新建一个线程,按顺序执行任务(非常有用)
  • 在并行队列中执行同步任务:不会新建线程,按顺序执行任务(几乎没用)

  • 在并行队列中执行异步任务:会新建多个线程,但是无法确定任务的执行顺序(有用,但是很容易出错)、

================================

关于GCD描述最详细的就是《Objective-C高级编程:iOS与OS X多线程和内存管理》这本书了,但是有的细节还是讲的不清楚。特别是串并行和同步异步的区别。网上找的资料也是千差万别。 经过对比、实验验证,下面应该是关于GCD的串并行,同步异步的区别最精准的描述了。


名称 功能特点 确定性解释 不确定性解释
同步 完成需要做的任务后才会返回,进行下一任务 “任务”,在 GCD 里指的是 Block;在 performSelector 方法中,对应 selector 方法。

同步方法,功能类似 dispatch_group_wait ,而 group 指的是所有线程,包括主线程。

不一定是多线程
异步 不会等待任务完成才返回,会立即返回。 异步是多线程的代名词,因为必定会开启新的线程,线程的申请是由异步负责,起到开分支的作用。 --
串行队列 任务依次执行 同一时间队列中只有一个任务在执行,每个任务只有在前一个任务执行完成后才能开始执行。 你不知道在一个Block(任务)执行结束到下一个Block(任务)开始执行之间的这段时间时间是多长,enter image description here
并行队列 任务并发执行 你唯一能保证的是,这些任务会按照被添加的顺序开始执行。但是任务可以以任何顺序完成 你不知道在执行下一个任务是从什么时候开始,或者说任意时刻有多个Block(任务)运行,这个完全是取决于GCD。enter image description here
全局队列 隶属于并行队列 不要与 barrier 栅栏方法搭配使用, barrier 只有与自定义的并行队列一起使用,才能让 barrier 达到我们所期望的栅栏功能。与 串行队列或者 global 队列 一起使用,barrier 的表现会和 dispatch_sync 方法一样。
主队列 隶属于串行队列 不能与 sync 同步方法搭配使用,会造成死循环


同步和异步的区别

名称 同步 异步
串行队列 不会新建线程,依然在当前线程上

类似同步锁,是同步锁的替代方案

常用
会新建线程,只开一条线程

一条线程就够了

每次使用 createDispatch 方法就会新建一条线程,多次调用该方法,会创建多条线程,多条线程间会并行执行
并行队列 不会新建线程,依然在当前线程上

会新建线程,可以开多条线程

iOS7-SDK 时代一般是5、6条, iOS8-SDK 以后可以50、60条

常用

参考链接: Why can't we use a dispatch_sync on the current queue?



情况一、同步+主线程

NSLog("之前 - %@", NSThread.currentThread())  
dispatch_sync(dispatch_get_main_queue(), { () -> Void in  
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())  
答案:

只会打印第一句:之前 - {number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。

解释:

1、主线程把大任务1放到主队列中开始执行

2、主线程打印之前 - {number = 1, name = main}

3、 dispatch_sync开启一个同步任务,把小任务1放到主线程中,并要求执行完自己才可以执行其他任务

4、但是大任务1是在主队列中执行的,而主队列是串行队列,必须等大任务1执行完毕才可以执行其他任务。而大任务1是整个pringLog函数,包括小任务1,现在大任务1和小任务1都要去执行完自己才可以执行其他任务,导致资源竞争,线程卡死。


情况二、异步+异步,同串行队列

因为异步执行不会阻塞当前线程,所以不会造成死锁。

因为这里是串行队列,所以这里的任务3还是使用当前的线程。

如下,可以看到小任务4和小任务3都在同一条线程执行,而且由于小任务4先添加进入队列(任务添加顺序:小任务2,小任务3,小任务4,小小人物3) 而且该队列为串行队列,先添加进入的小任务4执行完毕,才会去执行小小任务3

执行结果:

大任务1,大任务2,(小任务2,大任务3),小任务3,小任务4,小小任务33

PS:

(小任务2,大任务3)表示两个任务执行顺序不确定,下同


情况三、异步+异步,同并行队列

输出如下:

任务执行顺序:

大任务1,大任务2,(小任务2,大任务3),小任务3,(小小任务33,小任务4)

2,5的先后顺序无法确定,因为是异步并行执行,同理33,4也无法确定先后顺序。


情况四、异步+同步,同并行队列

执行结果:

任务执行顺序:

大任务1,大任务2,(小任务2,大任务3),小任务3,小小任务33,小任务4

说明:

大任务2,3因为是异步线程并行执行,所以无法结果2和5执行的先后顺序。 由于异步执行,会重新开一条新的线程,小任务2,3,4和小小任务3都在这条线程里面按顺序执行。

三个小任务按照FIFO添加进入进入同一个线程,不管前面的任务是否执行完毕。由于小任务3是同步执行,他会阻塞新线程(number=2),等到小小任务33完成后,才会取消阻塞,继续往下执行小任务4


情况五、异步+同步,同串行队列(死锁)

执行结果:

说明:

很明显 @"3--%@"和 @"4--%@"没有被打印出来!这是为什么呢?我们再来分析一下:

分析:

我们按执行顺序一步步来哦:

  1. 使用 DISPATCHQUEUESERIAL 这个参数,创建了一个 串行队列。
  2. 主线程打印出大任务1这句。
  3. dispatch_async 异步执行,创建新的线程,于是有了两条线程,一条当前主线程继续往下打印出大任务3这句, 新建一条线程b执行大任务2。
  4. 由于大任务2和大任务3是两条不同的线程同步执行,所以小任务2和大任务3的执行顺序不确定
  5. 小任务2执行完毕,添加小任务3,执行完毕(dispatch_sync也是一句代码,这句代码里面是被系统封装了,提供给我们一个这样的接口来给我们使用),添加小任务4。
  6. 串行队列等待小任务4执行完毕,但是这个时候小小任务3被插入了队列(小任务3插入的),在小任务4后面,因为是同步执行,他会要求必须先执行完自己的任务再执行其他的任务,但是由于小任务4先添加进入串行队列,所以必须先执行完小任务4才能执行其他的。这样两个任务竞争同一条线程,互不相让,导致死锁。


情况六、同步+异步,同串行队列

执行结果:

任务执行顺序:

大任务1,大任务2,小任务2,小任务3,小任务4,(小小任务33,大任务3)

说明:

由于小任务2,3,4是在串行队列里面执行,所以必须执行完了,才会执行下面的任务 小任务3执行的结果就是把小小任务33加入串行队列,然后必须等待小小任务33执行完毕才能继续添加任务,此处小小任务33是该串行队列中最后一个任务。 由于小小任务33和大任务3是在不同的线程里面执行,所以他们是并行执行的。


情况七、同步+异步,同并行队列

执行结果:

任务执行顺序:

大任务1,大任务2,小任务2,小任务3,(小任务4,小小任务33),大任务3


情况八、同步+同步,同串行队列(会死锁)

执行结果:

说明:

这里执行到小任务2就阻塞了,是因为进入到大任务2中,因为是同步任务,必须等到大任务2全部执行完毕(大任务2的block包括的所有的代码)才能执行其他block任务。

但是此时小任务3突然插进来,也是同步任务,要求必须先执行自己的小任务3。两个任务都要求先执行自己,那么就导致小任务3和大任务2竞争资源,造成卡死。


情况九、同步+同步,同并行队列

执行结果:

任务执行顺序:

大任务1,大任务2,小任务2,小任务3,小小任务33,小任务4,大任务3

说明:

1、和情况七对比,为什么不会死锁?

因为这里是并行队列,每个任务会按照FIFO放到当前线程(main线程),不管前一个任务有没有执行完毕,这样就不会造成阻塞。 不同于情况七,必须等到前一个任务执行完,才能继续把下一个任务放到线程上,这样会造成阻塞。

2、所有的任务为什么按照顺序执行?
因为是两处都是同步执行,必须等到前面一个block里面的任务执行完毕,才能执行下一个任务,所以按照顺序执行


还有八种情况分别如下:
  1. 同步+同步,不同串行队列
  2. 同步+同步,不同并行队列
  3. 同步+异步,不同串行队列
  4. 同步+异步,不同并行队列
  5. 异步+同步,不同串行队列
  6. 异步+同步,不同并行队列
  7. 异步+异步,不同串行队列
  8. 异步+异步,不同并行队列

上面八种情况由于处在不同队列,所以都不会造成线程阻塞,分析方法和列举的九种例子大同小异,就不在一一分析

下面是我在网上找资料的时候看到一些比较靠谱的GCD资料,分享给大家


参考资料:

comments powered by Disqus