iOS探索 多线程面试题分析
欢迎阅览iOS探究系列(按序阅览食用作用愈加)
- iOS探究 alloc流程
- iOS探究 内存对齐&malloc源码
- iOS探究 isa初始化&指向剖析
- iOS探究 类的结构剖析
- iOS探究 cache_t剖析
- iOS探究 办法的本质和办^ w e #法查找流程
- iOS探究 动态办法解析和消息转发机制
- iOS探究 浅尝辄止dyld加载w P & n I c o流程
- iOS探究 类的加载过程
- iOS探% 9 , b & y ,究 分类、类拓展的加载过程
- iO J R + B D q N (S探究 isa面试题剖析
- iOS探究 runtime面试题剖析
- iOS探究 KVC原理及自定义
- iOS探究 KVO原理及v o z +自定义
- iOS探究 多线程原理
- iOS探究 多线程之GCD运用
- iOS探究 多线程之GCD底层剖/ _ e x f P ( a }析
- iOS探究 多线程之NSOperation
- iOS探究 多线程面试题剖析
- iOS探究 八大锁剖析
写在前面
前面四篇文章别离介绍了多线程原理
、GCD的运用
、GCDn : 6 b J = ~ x 8底层原理
、NSOperation
,本文将剖析iOS面试中高频的多线程+ – q W j ] k面试题,希望各位看官都能答对(部分内容跟前几篇文章有点重复)
一、多线程的挑选方案
技术方案 | 简介 | 言语 | 线程生命周期 | 运用评率 |
---|---|---|---|---|
pthread | 一套通用的多k 4 o线程API 适用于Unix/Linux/Windows等体系 跨平台/可移植 运$ f k p y A & ! 6用难度大 |
C | 程` } .序员办理 | 几乎不必 |
NSThread | 运用! t ] C G愈; F A m C )加面向目标 简略易用,可C 5 W L e直接操作线程目标 |
OC | 程序员办理 | 偶然运用 |
GCD | 旨在替代NSThread等线程技. W s r !术 充分利用设备的多核 |
C | 主动办理 | 常常运用 |
NSOperation | 根据GCD(底层是GCD) 比GCD多了一些更简略实用的功能 运用愈加面向目标 |
OC | 主动办理 | 常常运用 |
留意:假如运用NSThready ? 3 v . 7 j的performSelector:withObject:afterDelay:
时需求增加到当时线程的runloop
中,因为在内部会创立一个NC E 6 +STimer
二、GC | t | , : . .D和NSOperation的比较
-
GCD
和NS6 n w 2Operat: a 0 % i _ion
的联系如下:-
GCD
是X W e ( $面向底层的C言语的API -
NSOperation
是用GCD
封装构建的,是GCD
的高级笼统
-
-
GCD
和NSOperation
的对比方下:-
GCD
履行功率更高,而且因为行列中履行的是由block
构成的使命,这是一个轻量级的数据w ~ & | 8 ? I W结构——写起来愈加便9 Z 5 S h @利 -
GCD
只支撑FIFO
的行列,而NSOpration
能够设置最大并发数、设置优先级、增加依靠联系等调整履行顺序 -
NSOpration
乃至能够跨行列Q + z – k 4 $ m F设置依靠联系,但是GCD
只能经过设置串行行列,或者在行列内增加barrier
使命才能操控履行顺序,较为杂乱 -
NSOperation
支撑KVO
(面向目标)能够检测operation是否正在履行、是否结束、是否撤销
-
- 实践项目中,许多时分只会用到异步操作,不会有特别杂乱的线程联系办理,所以苹v Q *果推崇的是优化完善、P / ) c运转快速的GCD
- 假如考虑异步操作之间的事务性、顺序性、依靠联系,比方多线程并发下载,GCD需求写更多的代码来完结,而NSOperation已经内建了这些支撑
- 不管是GCD仍是NSOperation,咱们接触的都是使命和行列,都没有直接接触到线程,事实上线程办理也的确不需u | k D L w求咱们操心,体系对于线程的创立、调度办理和开释都做得很好;而NSThread需求咱们自己去办理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销
三、多l o 2 , I O线程的@ V 1 V g { g s –运用场景
- 异步履行
- 将耗时操作放在子线程中,使其不堵` g ; k U `塞主线程
- 刷新UI
- 异步网络恳求,恳求结束
dispatch_get_main_queue()
回到主8 ^ O R 线程刷新UIK o g P 5 - 同一F w t h页面多个网络恳求运用
dispatch_groupj Q w 0 s l + M
统一调度刷新UI
- 异步网络恳求,恳求结束
-
dispatch_once
- 在
单例
中运用,一个类仅有一个实例且提供一个大局u + : . k l – C拜访点 - 在
method-Swizzling
运用保证办法只交流一次
- 在
-
dispatch_after
将使命延迟加入行列 -
栅门函数C E e A
可用作同步锁 -
dispatch_semaphorec T : Y o T &_t
- 用作锁保证线程安全
- 操控
GCD
的最大并发数
-
dispatch_source定时器
替代误差较大的NSTimer
-
AFNetworking
、SDWebImage
等闻名三方库中的NSOperation
运用 - …
四、线程池的原理

- 若
线程池巨细
小于中心线程池巨细
时- 创立线程履行使命
- 若
线程池巨细
大于等于中心线程池巨细
时- 先判别线程池工作行列是否已j e r 0 Z @ [满
- 若& U n m @ P F没满就将使命push进行列
- 若已满时,且
maximumPo& S ] o # U U ?olSize>corePoolSize
,将创立新的线程来履行使命 - 反之则交给
饱满策略
去处理
参数名 | 代表含义 |
---|---|
corePoolSize | 线程池的基本巨细(中心线程池巨2 t 7 d x细) |
maximumPool | 线程池的最大巨细 |
keepAliveTime | 线程池中超越corePoolSize树木的闲暇线程的最大存活时刻 |
unit | keepAliveTime参数的时刻单位 |
workQueue | 使命堵塞行列 |
threadFactory | 新建线程的工厂 |
han4 u Idler | 当提~ t {交的使命数超越maxmumPoolSize与workQueue之和时, 使命会交给RejectedExecutionHandler来处理 |
饱满策略有如下四个:
-
AbortPolicy
直接抛出RejectedExecutionExeception异常来阻挠体系正常运转 -
CallerRunsPolicy
将使命回退到调用者 -
DisOldestPol Y 7licy
丢掉等候最久的使命 -
DisCardPolicy
直接丢掉使命
五、栅门函数异同以及留意点
栅门函) * I k数两个API的异同:
-
dispate 1 ^ ` $ k P : Sch_bar? 9 | [ w arier_async
:能够操控l L X # d G行列中使p * 9命的履行顺序 -
dispatch_barrier_sync
:不只堵塞了行列的履行,也堵塞了线程的履行
栅门函数留意点:
- 尽量运用自定义的并; ] 5发行列:
- 运用
大局行列
起不7 J ) 5 L D D到栅门函数的作用 - 运用
大局行列
时因为对大局行列造成堵塞,或许致使体系其他调用大局行列的当地也堵塞从而导致溃散(并不是只有你在运用这个行列)
- 运用
- 栅门函数只能操控同一并发行列:打个比方,平时在运用
AFNetworking
做网络恳求w c ;时为什么不能用栅门函数起到同步锁堵塞的作用,因为AFNetworking
内部有自己的行列
六、栅门函数的读写锁
多读单写功能指的是:能够多个读者一起读取数据,而在读的时分,不能写入数据;在写的过程中不能有其他写者去写。即读者之间是并发的,写者与其他写者、读者之间是互斥的
- (id)V J TreadDataForKey:(NSString*)key {
__block id result;
dispatch_async(_concurrentQueue, ^{$ o ~ H D ~ 0 ] e
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString*)k( r ] * B f 3 Uey {
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
- 读:所有读者不知道读的先后顺序——并发行列+异步履行满意
- 写:写的那个时刻段,不能有任何读者+其他写者
-
d| O : uispatch_barrier_as6 L e ( *ync
满意:等行列中前面的读写使命都履行完了再来履行当时使命
-
七、GCD的并发量
不同于NSOperation
中能够经过maxConcurrentOperationCount
去操控并发数,GCD需求经过信号量才能达到作用
d{ $ { Rispatch_semaphore_t sem = dispatch_semaphore_creatG # ; $ _e(1);
dispatch_queue$ K X j h ) Z k 0_t queue = dispatch_queue_create("Felix", DISPATCHs ) Y_QUEUE_CONCURRENT);
for (int i = 0; i &k d w } x s llt; 10; i++) {
dispatl } Wch_async(queue, ^{
NSLog(@"当时%d----线程%@", i, [NSTz f v D L P % : 5hread currentThread]);
// 打印使命结束后信号量解锁
dispatch_semaphore_signal(sem)P I A o [ H;
});
// 因为异步履行,打印使命会较慢,所以这里信号量加锁
dispatch_semaphore_wait(sem, Dq w s ^ oISPATCH_TIME_FOREVER);
}
--------------------输出成果:-------------------
当时1----线程<NSThread: 0x600001448d40>{numb r ~ :ber = 3, name = (null)}
当时0----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当时2----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当时3----线程<NSThreaE 8 / M B 5 3d: 0x60000140c240>{number = 6, name = (null)}
当时4----线程<NSThreadg = , d 6 7 g h J: 0x60000140c240>{number = 6, name = (null)}
当时5----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当时6----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当时7----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当时8--l J q z--线程<NSThread: 0x600001448d40>{numbe! ] L Kr = 3, name = (null)}
当时9----线程<NSThread: 0x60000140c2F r p 140>{number = 6, name = (null)}
--------------------输出成果:-------------------
在面试中更多会考验开发人员对于指定场景的多线程常识,接下来就来看看一些归纳运用
八、归纳运用一
1.下列代l P [ { 2 v 2 ^ 8码会报错吗?
int a = 0;
while (h I X E P z Ua < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
- 编译会报错
Variable is no/ ? @ P A { S 1t assi$ T Z l {gnable (missing __block type specifier)
- 这块归于block的常识
- 捕获外界变量并进行修正需求加
__block int a = 0;
- 这块内容在接下来的
block
会讲到
- 这块内容在接下来的
2.下列代码的输出
__block int a = 0;
while (a < 5) {
dispatch_as, P $ [ c Tync(K z : $dispatch_geq u V e B Dt_global_queue(0,L ! K k # k 1 7 $ 0), ^{
a++;
});
}
N{ A G e W X 1 ( 7SLo? a ! | 0g(@"%d", a)^ 9 _ . [ J U o;
- 会输出
0
吗?- 不会,尽管是并发异步履行,但是有
while
在,不满意条件就不会跳出循环
- 不会,尽管是并发异步履行,但是有
- 会输出
1~4
吗?- 不会(原因请往下看)
- 会输出
5
吗?v f 1 ` +- 有或许(原因请往下看)
- 会输出
6~∞
吗?- 极有或i J f许
剖析:
- 刚进入while循环时,
a=0
,然后进行a++
- 因为是
异步并发
会拓荒子线程并有或许超车完结- 当
线程2
在a1 N v % M Y W=0
履行a++
时,线程3
有或许已经完结了a++
使a=1
- 因为是操作同一片内存空间,
线程3
修正[ ? [ 6 6 C U h了a
导致线程2
中a
的值也发生了改变 - 慢一: P o m s } : O拍的
线程2
对已经是a=1
进行a++
操作
- 当
- 同理还有
线程4
、线程5
、线程n
的存在- 能够这么理解,线程2、3、4、5、6一起在
a=0
时操作a
- 线程2、3、4、5按顺序完结了操作,此刻
a=4
- 然后
线程6
开端操作了,但是它还没履行完就跳到了下一次循环了拓荒了线程7
开端aV u |++
- 当
线程6
履行结束修正a=5
之后来到while条件判别
就会跳出循环 - 但是
I/O
输出比较耗时,此5 3 w ] v v C !刻线程7又刚好完结了再打印,就会输出大于5
- 能够这么理解,线程2、3、4、5、6一起在
- 也有那么种抱负状况B z X 5 H ` U,
异步并发
都比较听话,刚好在a=5
时没有子线程- 此刻就会输出
5
- 此刻就会输出
假如还没有理解能够在while循环中) 7 D增加打印代码
__block int a = 0;
while (a < 5), } h i I s . f {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d————%@", a, [NSThread currentThread]);
a++;
});
}
NSLog(@"此刻的%T . [ - 5 7d", a);

打印信息证明while* H n 7 %外面的打印已经履行,但是子线程仍是有 3 B a 6 ( $或许在对a进行操作的
3.怎样处理线程不安全?
或许有的小伙伴说这种需求不存在,但是咱们只管处理便是了
此刻咱们应该能想到一下几种处理方案:
- 同步函数替换异步函数
- 运用栅门函数
- 运用信号量
- 同步函数替换异步函数
- 成果:能满意需求
- 作Q ! * s h ] c z S用:不是很好——能运用9 } 异步函数去使唤子线程为什么不必呢(尽管会消耗内存,但是功率高)
- 运用栅门函数
- 成果:能满意需求
- 作用:一般
- 首先
栅门函数
和大局行列
搭配D v & E k { 4 9 E运9 6 $ # 4 N用会无效,需求更换行列类型; - 其次
dispatch_barrier_syncp ? O K { S Q Y Z
会堵塞线程,影响性能 - 而
dispatch_barrier_async
不能满意需求,它只能操控前面的使命履行结束再履行栅门使命(操控使命履行)但是异步栅门履行也是在子线程中,当a=4
时会先持续下一次循环增加使命到行列中,再来异步履行栅门使命(不能操控使命的增加)
- 首先
__block int a = 0;
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
while (a &T % O H _ : Nlt; 5) {
dispatch_async(queue, ^{
a++;
});g z # 5 5
dispatch_barrier_async(queue, ^{});
}
NSLog(@"此刻的%d", a);
sleep(1);
NSLog(@"此刻的%d", a);
--------------------输出成果:-------------------
此l } M $ Q (刻的2 * c .5
此刻的17
--------------------输出成果:-------% ^ . / ! 0------------
- 运用信号量
- 成果:能满意需求
- 作用:很好、简洁功率高
_x _ M_blX o ^ e ~ ` C 5 Eock int a = 0;
dispatch_semaphore_t sem = disp_ L Y Qatch_semaphore_creB K V D P date(0);
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
dispatch_si ] +emaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"此刻的%d", a);
sleep(1);
NSLog(@"此刻的%d", a);
--------------------输出成果:-------------------
此刻的5
此刻的5
--------------------输出成果:----------X v k E g E * ? u---------
九. m % ^ Y N、归纳运用二
1.输出内容
dispatch_queue_t qV { ]ueue = dispa} P ) + o a Ptch_queue_creaO | - W O ` S ~te("Felix", DISPATCH_QUEX u ` ( 8 jUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for* % r B E (int i =k V r ( 0; i < 1000; i++) {
dispatch_asyW M 9 N D +nc(queue, ^{
[marr addObject:@(i)];
});? ? } ; A F J G )
}
NSLog(@"%lu", mar? g ? mr.count);
- 你:输出一个小于1000的数,因为for循环中是异步操作
- 面试官:回去等消息吧
- 然后你回去之后试了下大吃一惊——程序崩了
这是为什么呢?
其实跟归纳运用一
是相同的道理——for循环异步时无数条线程拜访数组,造成了线程不安全
2.怎样处理线程不安全?
- 运用串行行列
diY E U 2 Aspatch_queue_t qu4 z * 4 Leue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSL N T v . D M YMutableArray *marr = @[].muP 9 V 2 !tableCop@ I z % yy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObjecA ~ ?t:@(i)];
});
}
NSLog(@"%l{ b , / (u", marr.count);
--------------------输出成果:-------------------
998
-------------[ + Z Z [ 1 m 6 y----6 , f n 2 Q v - )---输出成果:--D w ) 6 . q Q-----------------
- 运用互斥锁
dispatch_queue_t queue = dispatch_queue_create(1 q M t"Felix", DISPATCH_QUEUE_CONCU5 l & B :RRENT);
NSMutableArray *marr = @[].mutableCi 1 R i opy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
@synchronized (self) {
[marr addObject:@(i)];
}
});
}
NSLog(@"%lu"E J 7 , 3 . l 9 v, marr.co: 9 [ K + Xunt). Q g [ O X H;
--------v O y H O #------------输出成果:-------------------
997
--------------n 2 } j b `------输出成果:-F ) 8 6------------------
- 运用栅门函数
di~ 7 : U a A w ? 8spatch_queue_t queue = dispatch_queue_create("Felix", DISP/ n 0ATCH_QUEUE_CONCURRENT);
NSMutable0 D Z XArrayZ g O $ B V Q O u *marrP s a y 9 ! 8 p g = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[] b # V ^ 6 y Smarr addObject:@(i)];
});
dispatch_barrier_async(queue, ^{});
}
NSLog(@"%lu", marr.count);
3.剖析思路
单路千万条,跳跳通罗马——当然除了这三种还有其他办法
- 运用串行行列
- 尽管功率低,但总之能处理线程安全问题
- 尽管
串行异步
是使命一个接一个履行,但那是$ ; 0 ] F Z行列中的使命才满意履行规则 - 要想得到打印成果
1000
,能p h J : f N p ~够在行列中履行 - 总的来说,能满意需求` . o但不是很有效
- 运用互斥锁
-
@synchronized
是: j i ) S ` ,个好东西,简略易用还有效,但也没有满意咱们的需求 - 在for循环外运用行列内同步/异步都不能得到
100
- 要么先sleea * Zp一秒——这样不可控的代码是不可取的3 5 d j h D的
- 且在iOS^ Q T i W p S J的锁家族中
@synchronized? I 4 V
功率很低
-
- 运用栅门函数
- 栅门函数能够有效的操控使命的履行
- 且与
归纳运用一
不同,本题中是for循环 - 至于怎样得到打印成果
1u G M T 1 c - S k000
,只需求在同一行列中打印即可(栅门函| M X Z + f数的V m ~ e 2留c 1 i ` U U Z :意点)
dispatch_que5 2 3 g 2 : O eue_t queue = dispatch_queue_create("Felix", DISPATS G } L = ]CH_QUEUE_CONCURRENT);
NSMuta2 Y L E K W mblI i # N L ^ D veArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
dispatch_barriY d { r X | Ter_async(queue, ^{});
}
di? P E 2 Z : uspatch_async(queue, ^{
N G T YSLog(@"%lu", mS 2 3 %arr.count);
});
写在后边
多线程在日常开发中占有不少份量,一起面试中也是必问模块。但只有基X e 3础常识是一成不变的,归纳运用题稍有改动就是别的一种类型的常识考量了,而且也有多种处理方案