iOS开发中,多线程的解决方案有四种:pthread
,NSThread
,GCD
,NSOperation
,对于我自己来说GCD用的最多,而这篇文章只负责通过几个GCD
案例帮我们更好的理解线程队列,以及线程同步异步的关系,关于其他线程解决方案的原理和应用就不多赘述了。
一、GCD的特点
- GCD会自动利用更多的CPU内核。
- GCD自动管理线程的生命周期(创建线程,调度任务,毁掉线程等)。
- 程序员只需要告诉GCD想要如何执行什么任务,不需要编写任何线程管理代码。
二、GCD的基本概念
-
任务
block
:任务就是将要在线程中执行的代码,将这段代码用block
封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。 -
同步
sync
:一个接着一个,前一个没有执行完,后面不能执行,不开线程。 -
异步
async
:开启多个新线程,任务同一时间可以一起执行(异步是多线程的代名词)。 -
队列:装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列。
-
并发队列:线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步
dispatch_async
函数下才有效) -
串行队列:线程只能依次有序的执行。
GCD总结:将任务(要在线程中执行的操作
block
)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async
,同步dispatch_sync
)
三、API
了解了基本概念,这里在介绍下本文用到几个GCD API:
- 系统标准提供的两个队列
//全局队列,也是一个并发队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//主队列,在主线程中运行,因为主线程直有一个,所以这是一个串行队列
dispatch_get_main_queue();
- 我们还可以自己创建队列
// 从DISPATCH_QUEUE_SERIAL看出,这是串行队列
dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
// 同理,这是一个并发队列
dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 最后就是同步和异步线程了
//同步线程
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
//异步线程
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
四、案例分析
-
案例一
NSLog(@"1");//任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");//任务2
});
NSLog(@"3");//任务3
分析:输出结果:1,然后程序崩溃。
dispatch_sync
表示是一个同步线程;
dispatch_get_main_queue
表示运行在主线程中的主队列;
任务2是同步线程的任务。
首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是主队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。
-
案例二
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
分析:输出结果:1,2,3。
首先执行任务1,然后遇到一个同步线程,线程会进入等待。等待任务2执行完成后才能执行任务3。从dispatch_get_global_queue
可以看出,任务2被加入到了全局并发队列中,和主队列并不冲突,所以在并行队列执行完任务2之后,返回到主队列,执行任务3。
-
案例三
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
分析:控制台输出1,5,2(5/2顺序不确定),然后崩溃。
queue
是自己创建的串行队列。
首先执行任务1,遇到异步线程,将【任务2,同步线程,任务4】加入到串行队列 queue
中。因为是异步线程,所以主线程中的任务5不用等异步线程中的任务完成就可以执行(但是5和2谁先执行也不一定)。任务2执行完毕后,遇到同步线程,任务3加入到队列queue
中,以为任务4比任务3更早加入到串行队列queue
中,所以任务3要等任务4完成后才能执行,但是任务3所在的同步线程又会阻塞,所以任务4要等任务3完成后在执行,这就陷入了无限的等待中,造成死锁。
-
案例四
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
分析:控制台输出结果1,5,2,3,4(5/2顺序不一定)。
首先将【任务1,异步线程,任务5】加入到主队列中,异步线程中的任务是:【任务2,同步线程,任务4】。
先执行任务1,然后将异步线程的人物加到global_queue
中,因为是异步线程,所以任务5不用等待,直接可以执行,但2和5的顺序不一定。
然后再看异步线程中的任务执行顺序,任务2执行完成之后,遇到同步线程,但是同步线程中的任务是加到main queue
中的,所以在主队列中完成任务3,再回到全局队列完成任务4。
-
案例五
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
while (1) {
}
NSLog(@"5"); // 任务5
分析:控制台输出:4,1。(1/4顺序不一定)
加入到主队列中的任务【异步线程,任务4,死循环,任务5】。
加入到异步线程中的任务【任务1,同步线程,任务3】。
任务4完成之后,程序进入死循环,主队列阻塞。但是加入到全局队列中的异步线程不受影响,继续执行任务1后面的同步线程。
由于同步线程的任务2加入到主队列,而主队列阻塞,任务2无法执行,而任务3要等同步线程中的任务2完成才能执行,所以任务3也无法执行。
而由于死循环任务5也不会执行。
网友评论