iOS面试之多线程模块
2019-11-26 本文已影响0人
木子心语
多线程
多线程内容如下:
- GCD
- NSOperation
- NSThread
- 多线程与锁
1.GCD
- 同步/异步 和 串行/并发
- dispatch_barrier_async
- dispatch_group
- 同步/异步和串行/并发
//同步分派一个任务到串行队列
- dispatch_sync(serial_queue,^{//任务});
//异步废牌一个任务到串行队列
- dispatch_async(serial_queue,^{//任务});
//同步分派一个任务到并发队列
- dispatch_sync(concurrent_queue,^{//任务})
//异步分派一个任务到并发队列
- dispatch_async(concurrent_queue,^{//任务})
- 同步串行
-(void)viewDidload{
dispatch_sync(dispatch_get_main_queue(),^{
[self doSomething];
});
}
//产生死锁
原因:队列引起的循环等待
队列引起的循环等待.png
- 主队类先提交一个viewDidLoad,接着提交一个Block
- 两个任务都要提交到主线程去执行
- 我们分派viewDidLoad到主线程处理,需要调用Block,当Block同步调用完成之后,
viewDidLoad方法才可以继续向下执行
- viewDidLoad方法的调用结束,需要依赖于后续提交的Block任务
- Block想执行,需要依赖主队列先进先出的性质,需要等待ViewDidLoad的完成
- Block想要处理,依赖于ViewDidLoad的完成
- 所以,形成了相互等待
-(void)viewDidload{
dispatch_sync(serialQueue,^{
[self doSomething];
});
}
//没问题
同步串行.png
- viewDidLoad方法提交到主队列当中,会运行处理到主线程中
- viewDidLoad执行到某一时刻,需要同步提交一个任务到串行队列
- 串行队列其实是同步方式提交的,在当前线程执行,最终在主线程执行
- 串行队列任务,在主线程提交完成后,再到主队类完成viewdidload其他任务
- 同步并发
-(void)viewDidLoad{
NSLog(@"1");
dispatch_sync(global_queue,^{
NSLog(@"2");
dispatch_sync(global_queue,^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
//12345
//同步分派任务在当前线程中执行
- 异步串行
-(void)viewDidload{
dispatch_async(dispatch_get_main_queue(),^{
[self doSomething];
});
}
- 异步并发
-(void)viewDidload{
dispatch_async(global_queue,^{
NSLog(@"1");
[self performSelector:@selector(logPrint)] withObject: nil afterDelay:0];
NSLog(@"3");
});
}
-(void)logPrint{
NSLog(@"2");
}
//13
- 我们通过异步方式分派到全局并发队列中,本身block,会在GCD维护的线程池当中进行执行,处理.GCD默认并没有开启runloop
- performSelector: withObject: afterDelay: 延迟0秒提交Selector任务,需要相应创建提交runloop任务的逻辑的
- GCD创建的线程没有runloop的情况下, performSelector方法就会失效
- dispathc_barrier_async()
如何使用GCD实现多读单写?
- 读者,读者并发(实现多读)
- 读者,写着互斥(有读取数据的时候,不能存在写者写数据)
- 写着,写着互斥(有一个写线程在写数据,另一个写线程就不能写数据)
多读单写.png
dispathc_barrier_async(concurrent_queue,^{//写操作});
#import "User.h"
@interface User()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userDic;
}
@end
// 多读单写模型
@implementation User
- (id)init
{
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 异步调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userDic setObject:obj forKey:key];
});
}
- Dispatch_group_async()
使用GCD实现:A,B,C三个任务并发,完成后执行任务D
Dispatch_group_async.png
#import "Group.h"
@interface Group()
{
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation Group
- (id)init
{
self = [super init];
if (self) {
// 创建并发队列
concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle
{
// 创建一个group
dispatch_group_t group = dispatch_group_create();
// for循环遍历各个元素执行操作
for (NSURL *url in arrayURLs) {
// 异步组分派到并发队列当中
dispatch_group_async(group, concurrent_queue, ^{
//根据url去下载图片
NSLog(@"url is %@", url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的所有任务执行完成之后会调用该Block
NSLog(@"全部下载完成");
});
}
@end
2.NSOperation
需要与NSOperationQueue配合使用来实现多线程方案
优势特点:
- 添加任务依赖
- 任务执行状态控制
- 最大并发量
- 任务执行状态控制
状态有哪些?
- isReady 当前任务的就绪状态
- isExecuting 当前任务是否正在执行中
- isFinished 当前任务是否已经执行完成
- isCancelled 当前任务是否已经取消
状态控制
如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出
如果重写了start方法,自行控制任务状态
系统是怎样移除一个isFinished=YES的NSOperation的?
通过KVO
3.NSThread
NSThread启动流程.pngstart()
|
main()
|
perfomSelector
|
exit(结束线程)
4.多线程与锁
在日常开发中,都使用过哪些锁?
- @synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
- @synchronized
- 一般在创建单例对象的时候使用
- 多线程环境下创建线程是唯一的
- atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
@property(atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//这样保证线程的安全性
[self.array addObject:obj];//不能保证线程安全的
- OSSpinLock
- 自旋锁
- 循环等待访问,不释放当前资源
- 用于轻量级数据访问(引用计数+1/-1操作)
- NSLock
-(void)A{
[lock lock];
[self B];
[lock unlock];
}
-(void)B{
[lock lock];
//操作逻辑
[lock unlock];
}
//导致死锁
- 使用NSLock 对临界区加锁处理的时候,当前某个线程调用lock之后,获取得到锁
- 到B方法后,同一把锁友获取了一次,导致了死锁
//解决方案
通过递归锁NSRecursiveLock
- NSRecursiveLock
-(void)A{
[recursiveLock lock];
[self B];
[recursiveLock unlock];
}
-(void)B{
[recursiveLock lock];
//操作逻辑
[recursiveLock unlock];
}
递归锁的特点就是重入
- dispatch_semaphore_t
- 信号量
- dispatch_semaphore_create(1);
- dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
- dispatch_semaphore_signal(semaphore);
面试题:
- 怎样用GCD实现多读单写?
- iOS系统为我们提供几种多线程技术各自特点是怎样的?
- NSOperation对象在isFinished之后是怎样从queue当中移除掉的?
- 用过哪些锁?你是怎样使用的?
QQ交流群: 796142709