iOS 多线程
1.iOS几种多线程方案?
-
NSOperation (基于GCD)
-
可以添加任务依赖
-
任务执行状态的控制
-
isReady 当前任务是否处于就绪状态
-
isExecuting 当前任务是否处于正在执行状态
-
isFinished 当前任务是否已经完成
-
isCancelled 当前任务是否取消
可以通过重写start、main两个方法进行状态的控制。
-
如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出
-
如果重写了start方法,自行控制任务状态
-
-
可以控制最大并发量
-
-
GCD
-
Pthread
-
NSThread
1.1 系统是怎样移除一个isFinished = YES 的NSOperation的?
通过KVO来实现operation的状态控制
2. 理解并发队列,串行队列,同步,异步
队列:是先进先出的线性表。无论是并发队列,还是串行队列,都是按先进先出的顺序执行任务。
-
串行队列:说明队列中的任务串行执行,也就是一个一个的执行,必须等待上一个任务执行完成后,才能执行下一个任务。
-
并发队列:同样按照顺序执行任务,区别是,加入并发队列中有a、b、c、d四个任务,会先执行a,在a执行的过程中可以执行b,因此a、b具体哪个先执行完成是不确定的,具体同时能执行几个,由系统控制(GCD中不能直接设置并发数,可以通过信号量的方式实现,NSOperationQueue可以直接设置),但肯定是按照先进先出的原则调用的。
-
同步:在当前线程中顺序执行任务,执行当前任务完成之后才能继续执行下一个任务。并不会开启新线程。
-
异步:在新的线程中执行任务,不管调用的函数(任务)有没有执行完,都会进行下一个任务。且具备开启新线程的能力。
同步和异步的区别主要是向队列里面添加任务时是立即返回,还是等添加的任务完成之后再返回。
并发队列 | 手动创建的串行队列 | 主队列(串行执行任务) | |
---|---|---|---|
同步(sync) | 没有开启新线程/串行执行任务 | 没有开启新线程/串行执行任务 | 没有开启新线程/串行执行任务 |
异步(async) | 有开启新线程/并发执行任务 | 有开启新线程/串行执行任务 | 没有开启先线程/串行执行任务 |
注意:使用sync往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
2.下面代码会造成什么结果?
- (void)viewDidLoad {
NSLog(@"1");
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"2");
});
NSLog(@"3");
}
// 不会造成死锁 答案:1、2、3
// 因为1、3 跟2 不在同一个队列中
复制代码
- (void)interview01 {
NSLog(@"执行任务1");
dispatch_sync(dispatch_get_main_queue(),^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
//会造成死锁。同步线程相互等待。
//原因:同步队列中在任务1还没完成插入了任务2任务,
//正常同步队列应该在interview01任务完成后再执行任务3
//上述流程是在完成过程中立马插入任务2任务并执行,因此会造成死锁。
复制代码
- (void)interview02 {
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
// 不会死锁:主队列是串行执行任务。但是async异步,虽然在主队列不会开辟新的线程
// 但是在串行队列中不要求立马执行async异步任务
// 因此答案:1、3、2
复制代码
- (void)interview03 {
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL); // 串行队列
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
// 会造成死锁
// 串行队列中,先执行1然后因为async异步,所以执行5然后执行2
// 但是在async中 如果想要同步执行3,那么要先执行4,但是队列中又必须先执行3才能执行4
// 所以会死锁
复制代码
- (void)interview04 {
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL); // 串行队列
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
// 不会死锁 答案:1、5、2、3、4
复制代码
- (void)interview05 {
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
// 不会死锁:并行队列可以同步执行多个任务。答案:1、2、3、4、5
复制代码
- (void)GCD {
//全局队列:是系统开发的,直接拿过来(get)用就可以;与并行队列类似,但调试时,无法确认操作所在队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"3");
});
}
- (void)test {
NSLog(@"2");
}
// 打印结果:1 3
// performSelector:withObject:afterDelay:本质是往当前RunLoop中添加定时任务。
// 当前子线程默认没有开启RunLoop,执行3之后线程就销毁
复制代码
- (void)GCD2 {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
// 造成崩溃
// thread,因为线程没有开启RunLoop执行完毕后会释放线程
// performSelector:onThread:withObject:waitUntilDone: 找不到thread会造成崩溃
复制代码
3. 如何实现多读单写?
使用GCD dispatch_barrier (异步栅栏)
4. dispatch_group_async()
- 使用GCD实现:A、B、C三个任务并发,完成后执行任务D?
- (void)dispathcGroupAsync {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, _concurrent, ^{
NSLog(@"A");
});
dispatch_group_async(group, _concurrent, ^{
NSLog(@"B");
});
dispatch_group_async(group, _concurrent, ^{
NSLog(@"C");
});
dispatch_group_notify(group, _concurrent, ^{
NSLog(@"D");
});
}
复制代码
5.多线程锁
自旋锁:等待锁的时候处于忙等状态,一直占着CPU资源
互斥锁:等待锁的时候处于休眠状态
条件锁:满足条件锁打开继续运行,不满足条件时就进入休眠
递归锁:特点是可以重复加锁。重入
-
@synchronized
一般在创建单利对象的时候使用,来保证在多线程环境下创建对象是唯一的
-
atomic
属性关键字,对被修饰的对象进行原子操作(不负责使用)。如:array = [NSMutableArray array] 赋值可以保证原子性,[array addObject:obj] 对象操作不可以保证原子性。
-
OSSpinLock
属于自旋锁,目前已不安全,可能会出现优先级反转的问题。如:如果等待锁的线程优先级比较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
-
NSLock
如果同一把锁两次调用lock,那么会造成死锁,可以通过递归锁解决需要多次加锁的操作,或者使用不同的锁。
-
NSRecursiveLock
递归锁
-
dispatch_semaphore_t 信号量
dispatch_semaphore_wait:阻塞线程,是一个主动行为
dispatch_semaphore_singal:唤醒是一个被动行为
-
pthread_mutex
底层级的锁,NSLock等基本都是基于mutex实现