GCD
-
同步都是提交到当前线程执行。并且同步添加的任务必须马上执行。
-
串行队列是先进先出。主队列是串行队列。主队列提交到主线程。
-
异步才有开启线程的能力、但是异步提交到主队列会的任务提交到主线程上,不会开启新线程.
-
如果当前队列是串行队列,通过同步函数向当前队列中添加任务会造成死锁
-
主队列由系统自动创建,并与应用程序的主线程相关联。主队列上的任务一定是在主线程上执行(不过主线程并不是只执行主队列的任务,为了避免线程切换对性能的消耗,主线程还有可能会执行其他队列的任务)。主队列是一个串行队列,所以即便是在异步函数中也不会去开启新的线程。它只有在主线程空闲的时候才能调度里面的任务。
-
GCD开启的线程默认没有runloop,performSelector:withObject:afterDelay 需要提交到runloop上。所以在async开启的新线程中performSelector:withObject:afterDelay不会执行
-
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
-
使用sync函数往
当前
串行
队列中添加任务,会造成死锁
同步 | 异步 | |
---|---|---|
主线队列 | 无新线程,死锁 | 无新线程,串行执行 |
全局队列 | 无新线程,串行执行 | 有新线程,并发执行 |
自创串行队列 | 无新线程,串行执行 | 有新线程,串行执行 |
自创并发队列 | 无新线程,串行执行 | 有新线程,并发执行 |
- (void)interview1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{//这个位置改成dispatch_sync同步方法,就会打印132,因为任务是提交到主线程中,主线程的runloop是启动的。但是不会立即执行,所以打印132.
NSLog(@"1---%@",[NSThread currentThread]);
// [[NSRunLoop currentRunLoop] run]; //添加在此处会打印 13 这个位置是为什么?????????
[self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
// [[NSRunLoop currentRunLoop] run];//添加在此处会打印 123 这个位置是为什么?????????
NSLog(@"3---%@",[NSThread currentThread]);
// [[NSRunLoop currentRunLoop] run];//添加在此处会打印 132 这个位置是为什么?????????
});
}
- (void)test1{
NSLog(@"2---%@",[NSThread currentThread]);
}
//不添加runloop,则打印13。由于dispatch_async开启新线程,新线程默认没有runloop,而performSelector:withObject:afterDelay需要添加计时器到runloop中。如果是performSelector:withObject则打印123,因为不需要计时器添加到runloop中。
- (void)interview2{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
// 解决方法是线程保活
// 先向当前runloop中添加一个source(如果runloop中一个source、NSTime或Obserer都没有的话就会退出)
// 然后启动runloop
//[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSRunLoopCommonModes];
//[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test2) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test2{
NSLog(@"2---%@",[NSThread currentThread]);
}
//程序会崩溃,由于thread执行完block后就会释放,而test2又被提交到了已经释放的thread上。
- (void)interview3{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
});
NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
//死锁,由于任务被同步提交到主队列(串行队列)中。interview3和block形成了互相等待,导致死锁。
- (void)interview4{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
});
NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
//这和前面一个面试题相比只是把同步函数换成了异步函数。执行完任务1后,通过异步函数添加任务2,虽然异步函数有开启子线程的能力,但是由于是在主队列中,主队列的任务都是在主线程中执行,所以并不会开启子线程。由于是异步函数添加的任务2,所以不必等待任务2就可以继续往下执行,等当前任务(interview4)完成后串行队列再安排执行任务2。所以并不会造成死锁。
- (void)interview5{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//打印152然后死锁在执行任务3那。
- (void)interview6{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
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, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue2, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//15234
- (void)interview7{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
//15234
NSThread
实现一个常驻线程
NSOperation
对GCD的的封装,特点:
- 添加依赖
- 可对任务执行状态控制
- 控制最大并发
如果重写main方法,底层控制变更任务执行完成状态。如果重写start方法,需要自行控制状态。
多线程和锁
OSSpinLock(自旋锁):
线程处于盲等状态。一直试图访问锁。持续占用资源,适合轻量访问,比如+、-1等操作。iOS 10弃用,因为存在优先级反转问题。适用os_unfair_lock替代。最好是先增加判断来尝试加锁。 优先级反转:指低优先级的线程先加锁,然后cpu有可能把执行时间都留给高优先级的线程去忙等,造成没有时间去执行低优先级的线程,造成类似死锁的情况。比如thread1低优先级先加锁,然后cpu分配时间给高优先thread2去忙等,那么久会thread1和thread2互相等待。
pthread_mutex(互斥锁):
等待互斥锁的线程处于休眠状态,不占用cpu资源,但是唤醒线程需要一定时间,效率比自旋锁低。不会存在优先级反转问题
@synchronized:
对递归锁的封装,适用于单例。适用简单性能比较差。
atomic:
保证赋值线程安全,不保证操作线程安全。
NSLock:
对pthread_mutex(常规锁)
的封装,控制线程同步问题。
NSRecursiveLock:
对pthread_mutex(递归锁)
的封装,在递归调用中使用到。
NSCondition:
对pthread_mutex(条件锁)
的封装,NSConditionLock
也是一个条件锁,它是对NSCondition
的进一步封装
dispatch_semaphore_t:
是GCD中用于控制最大并发数的,其初始化时设置的信号量值就是最大并发数,当信号量值初始化为1时表示最大并发数为1,也就达到了同一时间只有一个线程访问要加锁代码块的目的,从而实现代码同步。
性能从高到低排序:
- os_unfair_lock 互斥锁 因为汇编会调用system call系统函数 去休眠
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
什么情况使用自旋锁比较划算?
轻量访问、cpu资源不紧张、多核处理器、等待时间短
什么情况使用互斥锁比较划算?
等待时间长、cpu资源占用高、单核、io操作、代码复杂、竞争紧张