文章预览
概述
上一篇文章我介绍了NSThread相关的API以及使用方法,本文我们继续来讲解另一套线程相关的API, 那就是GCD。我们在开发过程中用的最多也是GCD,面试中问道最多的也是GCD。通过阅读本文,希望您对GCD的理解有新的收获。本文将讲解一下内容:
个人认为,掌握好GCD最好的方式是彻底理解队列、同步、异步这些概念。而不是去死记硬背。死记硬背固然能应付一时,当终究不是正道,下面我们来一一解释这些概念。
队列
关于队列,我们需要明白队列是做什么的,我的理解是队列是用来存放任务的。而队列分为串行队列、并行队列。无论是串行队列还是并行队列,都是按照先进先执行的选择进行的。区别在于:对于串行队列而言,一次只能执行一个任务,该任务执行完毕之后才能执行下一个任务,而并行队列则不同,并行队列一个可以去取若干个任务执行,具体执行多个任务根据系统的环境不同而不同。下面讲述一下iOS系统中常用的队列。
队列名称 |
描述 |
主队列 |
主队列是一个串行队列,运行在主线程之中。 经常被用来从其他线程切换到主线程操作UI。 可以通过dispatch_get_main_queue() 获取到。 |
全局队列 |
全局队列是一个并行队列,可以并发的执行一个 或者多个任务,经常用来执行一些简便的异步任务 可以通过dispatch_get_global_queue("优先级", "") 来获取。 |
串行队列 |
串行队列是一次只能执行一个任务的队列,通常在当前线程环境下执行。 但多个串行队列是可以并行执行的。 |
并行队列 |
并行队列通常会开启多个线程来执行队列中的任务, 开启的线程数量根据系统决定,因为iOS的线程是有系统统一管理的。 |
加一句废话:对于GCD而言,一个block就是一个任务,一个任务在一个线程中执行。
同步与异步
同步和异步关注的是 消息通信机制 所谓同步,就是发出一个调用的时候,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
而异步则是相反,调用发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出之后,调用者不会立即得到结果。而是在调用发出后,被调用者通过状态,通知来告知调用者,或者通过回调函数处理这个调用。
我们来看一下GCD中两个关键的API,dispatch_async
、dispatch_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,指出下面代码的输出结果:
- (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,指出下面代码的输出结果:
- (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_sync
block的内部任务已经完成,所以(b)就先执行了。3s之后,执行(a)。
例3,指出下面代码的输出结果:
- (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的公众号
………………………………