欢迎阅读iOS探索系列(按序阅读食用效果更加)程序员
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 八大锁分析
前面四篇文章分别介绍了多线程原理
、GCD的应用
、GCD底层原理
、NSOperation
,本文将分析iOS面试中高频的多线程面试题,但愿各位看官都能答对(部份内容跟前几篇文章有点重复)面试
技术方案 | 简介 | 语言 | 线程生命周期 | 使用评率 |
---|---|---|---|---|
pthread | 一套通用的多线程API 适用于Unix/Linux/Windows等系统 跨平台/可移植 使用难度大 |
C | 程序员管理 | 几乎不用 |
NSThread | 使用更加面向对象 简单易用,可直接操做线程对象 |
OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替代NSThread等线程技术 充分利用设备的多核 |
C | 自动管理 | 常用 |
NSOperation | 基于GCD(底层是GCD) 比GCD多了一些更简单实用的功能 使用更加面向对象 |
OC | 自动管理 | 常用 |
注意:若是使用NSThread的performSelector:withObject:afterDelay:
时须要添加到当前线程的runloop
中,由于在内部会建立一个NSTimer
数组
GCD
和NSOperation
的关系以下:安全
GCD
是面向底层的C语言的APINSOperation
是用GCD
封装构建的,是GCD
的高级抽象GCD
和NSOperation
的对好比下:网络
GCD
执行效率更高,并且因为队列中执行的是由block
构成的任务,这是一个轻量级的数据结构——写起来更加方便GCD
只支持FIFO
的队列,而NSOpration
能够设置最大并发数、设置优先级、添加依赖关系等调整执行顺序NSOpration
甚至能够跨队列设置依赖关系,可是GCD
只能经过设置串行队列,或者在队列内添加barrier
任务才能控制执行顺序,较为复杂NSOperation
支持KVO
(面向对象)能够检测operation是否正在执行、是否结束、是否取消
- 实际项目中,不少时候只会用到异步操做,不会有特别复杂的线程关系管理,因此苹果推崇的是优化完善、运行快速的GCD
- 若是考虑异步操做之间的事务性、顺序性、依赖关系,好比多线程并发下载,GCD须要写更多的代码来实现,而NSOperation已经内建了这些支持
- 无论是GCD仍是NSOperation,咱们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不须要咱们操心,系统对于线程的建立、调度管理和释放都作得很好;而NSThread须要咱们本身去管理线程的生命周期,还要考虑线程同步、加锁问题,形成一些性能上的开销
dispatch_get_main_queue()
回到主线程刷新UIdispatch_group
统一调度刷新UIdispatch_once
单例
中使用,一个类仅有一个实例且提供一个全局访问点method-Swizzling
使用保证方法只交换一次dispatch_after
将任务延迟加入队列栅栏函数
可用做同步锁dispatch_semaphore_t
GCD
的最大并发数dispatch_source定时器
替代偏差较大的NSTimer
AFNetworking
、SDWebImage
等知名三方库中的NSOperation
使用线程池大小
小于核心线程池大小
时
线程池大小
大于等于核心线程池大小
时
maximumPoolSize>corePoolSize
,将建立新的线程来执行任务饱和策略
去处理参数名 | 表明意义 |
---|---|
corePoolSize | 线程池的基本大小(核心线程池大小) |
maximumPool | 线程池的最大大小 |
keepAliveTime | 线程池中超过corePoolSize树木的空闲线程的最大存活时间 |
unit | keepAliveTime参数的时间单位 |
workQueue | 任务阻塞队列 |
threadFactory | 新建线程的工厂 |
handler | 当提交的任务数超过maxmumPoolSize与workQueue之和时, 任务会交给RejectedExecutionHandler来处理 |
饱和策略有以下四个:数据结构
AbortPolicy
直接抛出RejectedExecutionExeception异常来阻止系统正常运行CallerRunsPolicy
将任务回退到调用者DisOldestPolicy
丢掉等待最久的任务DisCardPolicy
直接丢弃任务栅栏函数两个API的异同:多线程
dispatch_barrier_async
:能够控制队列中任务的执行顺序dispatch_barrier_sync
:不只阻塞了队列的执行,也阻塞了线程的执行栅栏函数注意点:并发
全局队列
起不到栅栏函数的做用全局队列
时因为对全局队列形成堵塞,可能导致系统其余调用全局队列的地方也堵塞从而致使崩溃(并非只有你在使用这个队列)AFNetworking
作网络请求时为何不能用栅栏函数起到同步锁堵塞的效果,由于AFNetworking
内部有本身的队列多读单写功能指的是:能够多个读者同时读取数据,而在读的时候,不能写入数据;在写的过程当中不能有其余写者去写。即读者之间是并发的,写者与其余写者、读者之间是互斥的异步
- (id)readDataForKey:(NSString*)key {
__block id result;
dispatch_sync(_concurrentQueue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString*)key {
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
复制代码
并发同步
获取到值后返回给读者
并发异步
则会先返回空的result 0x0
,再经过getter方法获取到值dispatch_barrier_async
知足:等队列中前面的读写任务都执行完了再来执行当前任务不一样于NSOperation
中能够经过maxConcurrentOperationCount
去控制并发数,GCD须要经过信号量才能达到效果async
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
// 打印任务结束后信号量解锁
dispatch_semaphore_signal(sem);
});
// 因为异步执行,打印任务会较慢,因此这里信号量加锁
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
--------------------输出结果:-------------------
当前1----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前0----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前2----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前3----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前4----线程<NSThread: 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----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前9----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
--------------------输出结果:-------------------
复制代码
在面试中更多会考验开发人员对于指定场景的多线程知识,接下来就来看看一些综合运用
int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
复制代码
Variable is not assignable (missing __block type specifier)
__block int a = 0;
block
会讲到__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
NSLog(@"%d", a);
复制代码
0
吗?
while
在,不知足条件就不会跳出循环1~4
吗?
5
吗?
6~∞
吗?
分析:
a=0
,而后进行a++
异步并发
会开辟子线程并有可能超车完成
线程2
在a=0
执行a++
时,线程3
有可能已经完成了a++
使a=1
线程3
修改了a
致使线程2
中a
的值也发生了变化线程2
对已是a=1
进行a++
操做线程4
、线程5
、线程n
的存在
a=0
时操做a
a=4
线程6
开始操做了,可是它还没执行完就跳到了下一次循环了开辟了线程7
开始a++
线程6
执行结束修改a=5
以后来到while条件判断
就会跳出循环I/O
输出比较耗时,此时线程7又恰好完成了再打印,就会输出大于5
异步并发
都比较听话,恰好在a=5
时没有子线程
5
若是尚未明白能够在while循环中添加打印代码
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d————%@", a, [NSThread currentThread]);
a++;
});
}
NSLog(@"此时的%d", a);
复制代码
打印信息证实while外面的打印已经执行,可是子线程仍是有可能在对a进行操做的
可能有的小伙伴说这种需求不存在,可是咱们只管解决即是了
此时咱们应该能想到一下几种解决方案:
栅栏函数
和全局队列
搭配使用会无效,须要更换队列类型;dispatch_barrier_sync
会阻塞线程,影响性能dispatch_barrier_async
不能知足需求,它只能控制前面的任务执行完毕再执行栅栏任务(控制任务执行)但是异步栅栏执行也是在子线程中,当a=4
时会先继续下一次循环添加任务到队列中,再来异步执行栅栏任务(不能控制任务的添加)__block int a = 0;
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
while (a < 5) {
dispatch_async(queue, ^{
a++;
});
dispatch_barrier_async(queue, ^{});
}
NSLog(@"此时的%d", a);
sleep(1);
NSLog(@"此时的%d", a);
--------------------输出结果:-------------------
此时的5
此时的17
--------------------输出结果:-------------------
复制代码
__block int a = 0;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"此时的%d", a);
sleep(1);
NSLog(@"此时的%d", a);
--------------------输出结果:-------------------
此时的5
此时的5
--------------------输出结果:-------------------
复制代码
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
}
NSLog(@"%lu", marr.count);
复制代码
- 你:输出一个小于1000的数,由于for循环中是异步操做
- 面试官:回去等消息吧
- 而后你回去以后试了下大吃一惊——程序崩了
这是为何呢?
其实跟综合运用一
是同样的道理——for循环异步时无数条线程访问数组,形成了线程不安全
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
}
NSLog(@"%lu", marr.count);
--------------------输出结果:-------------------
998
--------------------输出结果:-------------------
复制代码
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
@synchronized (self) {
[marr addObject:@(i)];
}
});
}
NSLog(@"%lu", marr.count);
--------------------输出结果:-------------------
997
--------------------输出结果:-------------------
复制代码
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
dispatch_barrier_async(queue, ^{});
}
NSLog(@"%lu", marr.count);
复制代码
单路千万条,跳跳通罗马——固然除了这三种还有其余办法
串行异步
是任务一个接一个执行,但那是队列中的任务才知足执行规律1000
,能够在队列中执行@synchronized
是个好东西,简单易用还有效,但也没有知足咱们的需求100
@synchronized
效率很低综合运用一
不一样,本题中是for循环1000
,只须要在同一队列中打印便可(栅栏函数的注意点)dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[marr addObject:@(i)];
});
dispatch_barrier_async(queue, ^{});
}
dispatch_async(queue, ^{
NSLog(@"%lu", marr.count);
});
复制代码
多线程在平常开发中占有很多分量,同时面试中也是必问模块。但只有基础知识是一成不变的,综合运用题稍有改动就是另一种类型的知识考量了,并且也有多种解决方案