前言
本篇文章不会介绍GCD的底层实现!!!
本篇文章不会介绍GCD的基本使用!!!
本篇文章是我这两天再次理解GCD后的记录。说实话,虽然在开发时知道多线程是什么、如何用,但是对于概念真的有点模棱两可,而线程死锁之类的问题更是一知半解。
正文
一、GCD的官方定义
开发者需要做的只是定义想执行的任务并将其追加到适当的Dispatch Queue中。
二、Dispatch Queue
下面先来说说定义中的Dispatch Queue
dispatch_async(queue, ^{
//block
});
以上代码的意义是:用dispatch_async这样的函数将block追加到Dispatch Queue中。而block内容便是想执行的任务。而Dispatch Queue,指的就是执行处理的等待队列。
Dispatch Queue有着先进先出的原则。他会把你先放进去的任务先拿出来执行。
Dispatch Queue有两种,分别为Serial Dispatch Queue 和 Concurrent Dispatch Queue。
就像图片所示,Serial Dispatch Queue也就是串行队列,他会一个个把任务拿出来执行,并且在拿下一个任务的时候,上一个任务必须已经是执行完毕了。
而Concurrent Dispatch Queue也就是并行队列,他会把任务按照顺序快速的拿出来,因为他不需要等待上一个任务的执行完毕。
三、同步&&异步
同步:阻塞当前线程,必须要等待同步线程中的任务执行完,返回以后,才能继续执行下一任务。
异步:不会阻塞当前线程,会开启新的线程而不需要等待当前线程的任务执行完毕。
因为我一开始总是把多线程和异步搞混,所以我理解成多线程只是实现异步的手段,而异步是目的。
四、同异步与不同队列的组合
这里可以先打个比方,Dispatch Queue相当于是领导,而线程相当于是工人,领导可以串行发布任务,也可以并发发布任务(串行并发),工人有一人或多人(同步异步)。
1.串行队列同步执行
Serial Dispatch Queue把任务一个个拿出来,线程一个个的执行拿出来的任务。
2.并行队列同步执行
Concurrent Dispatch Queue会快速的把任务都拿出来,但是线程就只有一个,还是只能一个个得执行任务,所以并没有多线程执行。
3.串行队列异步执行
Serial Dispatch Queue会把任务一个个拿出来,但拿出下一个前提是上一个任务已经被线程执行完毕了。所以这里会有多个线程来等待执行任务,但是任务却一个个出来,真是浪费了这么多线程资源啊。
4.并行队列异步执行
这里任务快速拿出,线程快速执行,就实现了多线程操作,提高效率。
这样一来对于GCD的理解是否一下子清晰了~
五、GCD的小测试——线程死锁
既然对于GCD有了更加清晰的了解,那么看到有关线程死锁的问题你能否一眼看出呢。
第一种:
代码:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"----------1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"----------2");
});
NSLog(@"----------3");
}
结果:
2017-04-01 15:08:49.142 MRCTest[4201:334649] ----------1
分析:
首先打印1肯定没问题,但是为什么2,3没了呢?其实是这样的:由于是主队列同步执行而且block是后下入主队列的,所以block会放到主队列的后面等待主队列执行完毕后再执行,所以2是放在3的后面的。但是主线程也在等block执行完毕,这样主线程才会继续执行。也就是说3又在等2执行完毕才会执行。所以出现了死锁。
可能有点绕,但是想通了却是很简单哟。
第二种:
代码:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"----------1");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"----------2");
});
NSLog(@"----------3");
}
结果:
2017-04-01 15:31:39.474 MRCTest[4380:348251] ----------1
2017-04-01 15:31:39.543 MRCTest[4380:348251] ----------2
2017-04-01 15:31:39.555 MRCTest[4380:348251] ----------3
分析:
本次为同步全局队列。虽然主线程队列会等待2的执行,但是2这次没有放在3的后面而是在另一个全局队列中,所以不会造成死锁。
第三种:
代码:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"----------1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"----------2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"----------3");
});
NSLog(@"----------4");
});
NSLog(@"----------5");
}
结果:
2017-04-01 15:42:41.212 MRCTest[4523:355983] ----------1
2017-04-01 15:42:41.212 MRCTest[4523:355983] ----------5
2017-04-01 15:42:41.212 MRCTest[4523:356121] ----------2
2017-04-01 15:42:41.231 MRCTest[4523:355983] ----------3
2017-04-01 15:42:41.231 MRCTest[4523:356121] ----------4
分析:
这次有点复杂了,我们慢慢来。
首先打印1,没毛病。然后将
NSLog(@"----------2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"----------3");
});
NSLog(@"----------4");
这个block放到了全局队列中,而且是异步的,说明主队列不需要等待这个block的执行,所以会打印5。
再看block中,打印2没毛病,然后又同步的把3放到了主队列后面。注意:这里看似和第一种类似好像会造成死锁,其实不是的,这次主队列其实已经运行完啦。不信你看5打印了没~
第四种:
代码:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"----------1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"----------2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"----------3");
});
NSLog(@"----------4");
});
NSLog(@"----------5");
while (1) {
}
NSLog(@"----------6");
}
结果:
2017-04-01 15:51:47.152 MRCTest[4579:360952] ----------1
2017-04-01 15:51:47.153 MRCTest[4579:360952] ----------5
2017-04-01 15:51:47.153 MRCTest[4579:360999] ----------2
分析:
本次只是在第三种的基础上加了一个while循环。但是却只打印了152。原因其实也很简单,这次主线程没有执行完啊,6不是没打印嘛~所以3还是放在主线程队列后面,但是主线程没有执行完,不会执行3。而3是同步的,所以3不执行完4也不会打印。
小结
这种判断线程死锁的问题,最主要的还是思路需要清晰。遇到同步方法,就会阻塞后面的代码,必须先执行block中的代码。而遇到异步的方法,先不管block,反正后面的代码肯定会执行。但是执行顺序就得看加到哪个队列去了~
总结
上面的概念、代码已经解释都是我近几天的研究的成果,终于从一知半解到基本掌握了。如果你再看线程死锁那部分还是很绕的话,那就自己敲敲看,然后结合我的解释再一行行分解看,肯定可以掌握的~
更新 (17.12.04)
在看了MobileProject的代码后,发现了里面存在一些与GCD相关的代码,也把它记录下来。
一、并发队列 + 同步执行
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncConcurrent---end");
// syncConcurrent---begin
// 1------{number = 1, name = main}
// 1------{number = 1, name = main}
// 2------{number = 1, name = main}
// 2------{number = 1, name = main}
// 3------{number = 1, name = main}
// 3------{number = 1, name = main}
// syncConcurrent---end
分析:因为这里是同步执行,所以不会开启新线程,并且会阻塞当前线程。因此syncConcurrent---end
会最后输出,123也会按照顺序打印,并且打印的线程都为主线程。
二、并发队列 + 异步执行
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.asyncqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncConcurrent---end");
// asyncConcurrent---begin
// asyncConcurrent---end
// 3------{number = 9, name = (null)}
// 3------{number = 9, name = (null)}
// 1------{number = 10, name = (null)}
// 1------{number = 10, name = (null)}
// 2------{number = 5, name = (null)}
// 2------{number = 5, name = (null)}
分析:因为这里为异步执行,不会阻塞当前线程,并具备开启新线程能力。所以asyncConcurrent---end
会立马打印出来。123打印的顺序也不会固定,因为都是不同的线程同时执行打印出来的。
三、串行队列 + 同步执行
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.syncSerial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncSerial---end");
// syncSerial---begin
// 1------{number = 1, name = main}
// 1------{number = 1, name = main}
// 2------{number = 1, name = main}
// 2------{number = 1, name = main}
// 3------{number = 1, name = main}
// 3------{number = 1, name = main}
// syncSerial---end
分析:和一类似。
四、串行队列 + 异步执行
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.asyncSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncSerial---end");
// asyncSerial---begin
// asyncSerial---end
// 1------{number = 4, name = (null)}
// 1------{number = 4, name = (null)}
// 2------{number = 4, name = (null)}
// 2------{number = 4, name = (null)}
// 3------{number = 4, name = (null)}
// 3------{number = 4, name = (null)}
分析:异步执行,因此asyncSerial---end
会立马打印不需等待。因为是串行队列,123会依次放入到这个队列中,因此123顺序打印。并且个人认为,为了资源不浪费,所以只新开了一个线程。二中因为是并发队列,123一起执行,所以一个线程完成不了任务,才创建了多个线程。
五、主队列 + 同步执行
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncMain---end");
// syncMain---begin
分析:线程死锁,参照上面测试代码。
六、主队列 + 异步执行
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncMain---end");
// asyncMain---begin
// asyncMain---end
// 1------{number = 1, name = main}
// 1------{number = 1, name = main}
// 2------{number = 1, name = main}
// 2------{number = 1, name = main}
// 3------{number = 1, name = main}
// 3------{number = 1, name = main}
分析:和四类似。
七、全局队列+ 异步执行
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 异步执行
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"asyncGloba:%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
你猜