我们在面试的时候经常会遇到很多关于多线程的面试题,这些面试题的答案你都知道吗?
1.你理解的多线程是什么?
2.iOS的多线程方案有哪些?你更倾向于哪一种?为什么?
3.你在项目中用过GCD吗?有哪些使用?
4.GCD的队列类型有哪些?
5.什么情况下会产生死锁?
6.说一下OperationQueue和GCD的区别,以及各自的优势是什么?
7.线程安全的处理手段有哪些?
8.OC你了解的锁有哪些?在你回答的基础上进行二次提问;
追问一:自旋锁和互斥锁对比?
追问二:使用以上锁需要注意哪些?
追问三:用C/OC/C++,任选其一,实现自旋或互斥锁,口诉即可.
.....
相信以上的前面的几个问题,大家都是很容易回答出来的,但是后面的关于锁的问题,我们可能用得比较少,比如加锁,解锁、自旋锁、互斥锁等等,今天就让我们一起了解这些东西,面试官问到多线程,那就是随便问,我们也能随便答.首先我们简单来回顾一下基础的知识.
iOS多线程有哪些?
pthread:是一个通用的多线程API,适用于Unix\Linux\Windows等多个系统,可以跨平台,但是由于它的语言是C语言,而且内存是我们程序员自己管理,所以使用难度比较大,而且几乎是不用的.
NSThread:使用起来更加面向对象,简单易用,可以直接操作线程对象,它的使用语言是OC,它的内存也是由我们程序员自己管理,所以偶尔使用
GCD:旨在替换NSThread等线程技术,它的一个优点可以充分利用设备的多核,它的语言是C语言,线程的生命周期是自动管理,所以用起来比较方便,所以经常被使用
NSOperation:它是基于GCD(底层是GCD),它比GCD多了一些更简单实用的功能,使用起来更加面向对象,它的语言也是OC,线程的生命周期也是自动管理,所以用起来也比较方便,也是经常被使用
下面用一张图,放在一起对比,看着比较明显和好记:
再补充一点:NSThread、GCD、NSOperation底层都是基于pthread.
我们用得比较多的是用GCD,我自己也是喜欢用GCD.下面就来介绍一下
GCD的常用函数有哪些?
1.用同步的方式来执行任务:
dispatch_sync(queue, block); queue:队列, block:任务.
2.用异步的方式来执行任务:
dispatch_async(queue, block); queue:队列, block:任务.
number=1是代表主线程,number=不是1就是子线程.下面是源码下载地址(网络不好的可能要开vpn才能打开)
GCD源码下载地址
GCD的队列
GCD的队列可以分为2大类型
1.并发队列 (Concurrent Dispatch Queue)
可以让多个任务并发 (同时) 执行 (自动开启多个线程同时执行任务)
并发功能只有在异步 (dispatch_async)函数下才有效
2.串行队列 ( Serial Dispatch Queue)
让任务一个接一个地执行 (一个任务执行完毕后,再执行下一个任务)
同步、异步、并发、串行 4个术语的区分
同步和异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
有个小提问:
dispatch_async(queue, block)在执行任务的时候,一定开启新的线程吗?
答案是否定的,因为我们知道有个特殊的串行队列,就是主队列,只要是主队列,不管你是同步还是异步线程,都是在当前线程执行,不会开启新的线程,请看下图:
这样的话就有同步、异步情况下有手动创建的串行、并发、主队列组合6种情况.
请看下图:
接下来,我们就说一下,面试题中常见的线程死锁问题.
什么情况下会产生死锁?
所谓死锁就意味着卡住了,这个线程不会往下执行了.请问以下代码会不会产生死锁:
(案例1我会仔细解释,后面的死锁案例就不详细解释了,道理都是一样的,下面一句话总结)
案例一:
请问上面的案例会不会出现死锁?答案是会的,首先队列的特点是:FIFO,first in first out,先进先出.因为我们知道dispatch_sync是同步方法,它不会开启新的线程,所以它肯定也是在主线程执行的,所以任务1执行完了,肯定执行任务2,而任务3在等任务2执行完毕;
同样的queue是主队列,而任务2是放在主队列里面的,而任务2主队列前面是viewDidLoad方法,所以任务2会等viewDidLoad执行完,也就是任务2会等任务3执行完才会执行,那就很明显了着2个任务相互等待造成死锁!
我们看一下运行结果:
直接崩溃.如果用画图来解释一下,也是比较清晰,请看下图
所以看图就很清晰,想取出任务2必须viewDidLoad执行完毕,viewDidLoad执行完毕必须任务3执行完毕,而任务3执行完毕,必须要执行完sync,而sync又取不出来任务2,所以造成死锁.我们也可以证明任务2只要是主队列中,它肯定是在viewDidLoad后面执行.我们只要把上面的同步改成异步即可,请看下图:
案例二:
这个会不会死锁?答案是会死锁.首先block0和block1都是在同一个queue中,block1在后面,而block1是同步的dispatch_sync所以会等block0执行完,再执行block1,所以是任务3等任务4;而dispatch_sync是同步的,任务4又会等任务3执行完!相互等待,死锁!
看一下执行结果:
案例三:
不会死锁
案例四:
不会死锁
从上面的例子,大家是不是觉得:只要是同步的,而且往同一个队列加任务就会死锁呢?
那么请看下面的案例
案例五:
当前就是同步的,而且往同一个队列加任务!此时是不会产生死锁.队列必须也是串行队列才是死锁!
所以通过以上的案例,我们得出以下总结,只要记住这句话,我们就知道什么情况下会出现死锁.
死锁总结:
使用sync函数往当前串行队列中添加任务
会卡住当前的串行队列(产生死锁)
注意条件:
1.sync函数
2.当前串行队列:同一个队列而且是串行队列
知道这个结论我们就可以知道什么时候产生死锁!辨别死锁就非常简单,上面的案例都适用,大家可以自己看下.
队列组的使用
比如我们现在有一个需求是:异步并发执行任务1、任务2;等任务1、任务2都执行完毕后,再回到主线程执行任务3,这个时候我们就用到GCD的一个队列组的应用.
这里任务3也可以精简写,直接把queue改成main_queue就可以;还有个就是我们还可以添加任务4,也是和任务3一样的写法,那么任务3和任务4就会等任务1和任务2执行完以后,交替执行!
由于多线程内容较多,我会在接下来的博客继续介绍!(如:多线程的其他面试题、多线程所有的锁对比及使用等).