iOS多线程编程二

概述

上一篇文章我介绍了NSThread相关的API以及使用方法,本文我们继续来讲解另一套线程相关的API, 那就是GCD。我们在开发过程中用的最多也是GCD,面试中问道最多的也是GCD。通过阅读本文,希望您对GCD的理解有新的收获。本文将讲解一下内容:

  • 队列
  • 同步与异步
  • 举例

个人认为,掌握好GCD最好的方式是彻底理解队列、同步、异步这些概念。而不是去死记硬背。死记硬背固然能应付一时,当终究不是正道,下面我们来一一解释这些概念。

队列

关于队列,我们需要明白队列是做什么的,我的理解是队列是用来存放任务的。而队列分为串行队列、并行队列。无论是串行队列还是并行队列,都是按照先进先执行的选择进行的。区别在于:对于串行队列而言,一次只能执行一个任务,该任务执行完毕之后才能执行下一个任务,而并行队列则不同,并行队列一个可以去取若干个任务执行,具体执行多个任务根据系统的环境不同而不同。下面讲述一下iOS系统中常用的队列。

队列名称 描述
主队列 主队列是一个串行队列,运行在主线程之中。
经常被用来从其他线程切换到主线程操作UI。
可以通过dispatch_get_main_queue() 获取到。
全局队列 全局队列是一个并行队列,可以并发的执行一个
或者多个任务,经常用来执行一些简便的异步任务
可以通过dispatch_get_global_queue("优先级", "")来获取。
串行队列 串行队列是一次只能执行一个任务的队列,通常在当前线程环境下执行。
但多个串行队列是可以并行执行的。
并行队列 并行队列通常会开启多个线程来执行队列中的任务,
开启的线程数量根据系统决定,因为iOS的线程是有系统统一管理的。

加一句废话:对于GCD而言,一个block就是一个任务,一个任务在一个线程中执行。

同步与异步

同步和异步关注的是 消息通信机制 所谓同步,就是发出一个调用的时候,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。

而异步则是相反,调用发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出之后,调用者不会立即得到结果。而是在调用发出后,被调用者通过状态,通知来告知调用者,或者通过回调函数处理这个调用。

我们来看一下GCD中两个关键的API,dispatch_asyncdispatch_sync

dispatch_async:

该函数需要两个参数,第一个参数是一个队列,第二个参数是一个block。

它的作用是:将block提交给队列,调用该方法会在block提交给队列之后立即返回,不会等待block的调用。而队列决定了该block是串行执行还是和该队列里其他任务同时执行。独立的串行队列之间可以同时执行。

dispatch_sync:

dispatch_async一样该函数同样需要两个参数。

它的作用是将block提交给队列并同步执行。与dispatch_asyn不同的是该函数不会立即返回,它会等到block的内容执行完毕之后才返回。

在当前队列中调用该函数,并将当前队列指定为目标队列会造成死锁。

dispatch_async不同的是,不会对目标队列执行retain操作,因为该函数的调用是同步的,它”借用”调用者的引用,同时也不会对block执行block_copy操作。

该函数会尽可能的在当前线程上调用该block。

了解了队列、同步/异步、dispatch_async/dispatch_sync之后,我们来看一下CGD的一些使用场景。

CGD的使用

例1,指出下面代码的输出结果:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("RiverLi", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", NSThread.currentThread);
});
NSLog(@"11111111------>%@", NSThread.currentThread);
}

执行结果是:

{number = 1, name = main}

11111111——>{number = 1, name = main}

这段代码其实很好理解,dispatch_sync 等待block执行完毕之后在执行其之后的代码。

例2,指出下面代码的输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("RiverLi", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"%@", NSThread.currentThread); //(a)
});
});
NSLog(@"11111111------>%@", NSThread.currentThread); //(b)
}

执行结果是:

11111111——>{number = 1, name = main}

{number = 3, name = (null)}

分析:为什么不是先打印(a)的信息,对于dispatch_sync 而言,他需要等待block的执行,而block内部是一个dispatch_async 它提交block之后就立即返回,而它返回之后dispatch_syncblock的内部任务已经完成,所以(b)就先执行了。3s之后,执行(a)。

例3,指出下面代码的输出结果:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", NSThread.currentThread);
});
NSLog(@"11111111------>%@", NSThread.currentThread);
}

执行结果是:

崩溃

解释:首先需要注意viewDidLoad方法是在主队列中执行的,调用dispatch_sync 并传入主队列。我们知道主队列是串行队列,对串行队列而言任务是一个执行完毕之后再执行另一个任务。上面的代码主队列正在执行某一项任务,当执行到dispatch_sync方法时,向主队列中添加了一个任务,并等待该任务的执行完毕,然后这是主队列正在执行包含dispatch_sync方法的任务,这样就造成了两个任务之间的彼此等待,而造成系统崩溃。

关于GCD源码的理解

参考

Dispatch Queues
严肃的问答

其他

本文首发于RiverLi的个人公众号,如需转载请与我本人联系。
由于本博客暂未开放评论功能,如有任何问题,请关注我的公众号(RiverLi),给我留言,或者在对应的文章评论。
RiverLi的公众号