您的位置:首页 > 其它

GCD线程死锁解锁案例分析

2016-12-04 14:16 495 查看

介绍GCD?

Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法。该方法在 Mac OS X 10.6 雪豹中首次推出,并随后被引入到了 iOS4.0 中。GCD 是一个替代诸如 NSThread, NSOperationQueue, NSInvocationOperation 等技术的很高效和强大的技术。

任务和队列

看下最简单的GCD异步把任务加入全局并发队列的代码
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务");
});


任务

任务其实就是一段想要执行的代码,在GCD中就是Block,就是C代码的闭包实现,需要详细了解Block的请戳2分钟明白Block,因此,做法非常简单,例如
reloadTableView
就可以加到这里去,问题在于任务的执行方式同步执行异步执行 ,这两者最简单可以概括为 是否具有开线程的能力

展开来说就是是否会阻塞当前线程,如果和上面示例代码所示,是async,他不会阻塞当前线程,block里面的任务会在另一个线程执行,当前线程会继续往下走,如果是sync,那么Block里面的任务就会阻塞当前线程,该线程之后的任务都会等待block的任务执行完,如果比较耗时,线程就会处于假死状态

队列

上面讲的是任务的同步执行或者异步执行,那么队列就是用于任务存放,分别有串行队列并行队列

队列都遵循FIFO(first in first out),串行队列根据先进先出的顺序取出来放到当前线程中,二并行队列会把任务取出来放到开辟的非当前线程,也就异步线程中,任务无限多的时候不会开无限个线程,会根据系统的最大并发数进行开线程

简单概括如下:

项目同步(sync)异步(async)
串行当前线程,顺序执行另一个线程,顺序执行
并发当前线程,顺序执行另一个线程,同时执行
可以看出同步和异步就是开线程的能力,同步执行必然一个个顺序执行在当前线程,而异步执行可以根据队列不同来确定顺序还是同步并发执行

队列的创建 和 简单API

主队列:
dispatch_get_main_queue();
主线程中串行队列


全局队列:
dispatch_get_global_queue(0, 0);
全局并行队列


自定义队列

// 自定义串行队列
dispatch_queue_create(@"custom name of thread", DISPATCH_QUEUE_SERIAL);
// 自定义并发队列
dispatch_queue_create(@"com.mkj.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);


最简单的API

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, ^(void)block)
dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, ^(void)block)


线程死锁1

NSLog(@"任务1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2");
});
NSLog(@"任务3");


打印信息

2016-12-04 12:50:55.932 GCD[3020:116100] 任务1
(lldb)
Exc_bad_INSTRUCTION报错


分析:

首先执行任务1,然后遇到
dispatch_sync
同步线程,当前线程进入等待,等待同步线程中的任务2执行完再执行任务3,这个任务2是加入到mainQueue主队列中(上面有提到这个是同步线程),FIFO原则,主队列加入任务加入到队尾,也就是加到任务3之后,那么问题就来了,任务3等待任务2执行完,而任务2加入到主队列的时候,任务2就会等待任务3执行完,这个就赵成了死锁。



线程死锁2

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);

NSLog(@"任务1");
dispatch_async(serialQueue, ^{
NSLog(@"任务2");
dispatch_sync(serialQueue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");


打印日志:

2016-12-04 13:10:06.587 GCD[3322:129782] 任务1
2016-12-04 13:10:06.588 GCD[3322:129782] 任务5
2016-12-04 13:10:06.588 GCD[3322:129815] 任务2
(lldb)  同样在这里报错停止


分析

1.这里用系统create的方法创建自定义线程,按顺序先执行任务1

2.然后遇到一个异步线程,把任务2,同步线程(包含任务3),任务4这三个东西看成一体放到自定义的串行队列中,由于是异步线程,直接执行下一个任务5,因此异步线程的任务2和任务5不确定谁先谁后,但是任务1 任务2 任务5这三个东西必定会打印出来

3.看下异步线程里面,都放置在自定义的串行队列中,任务2之后遇到一个同步线程,那么线程阻塞,执行同步线程里面的任务3,由于这个队列里面放置的任务4按第二步里面的顺序率先加入进串行队列的,当同步线程执行的时候,里面的任务3是还是按照FIFO顺序加入到任务4之后,那么又造成了案例一里面的任务4等待任务3,任务3等待任务4的局面,又死锁了







线程之间的调度,安全避开死锁

NSLog(@"任务1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务3");
});
NSLog(@"任务4");

});
NSLog(@"任务5");


打印日志:这里不会产生死锁,直接解释下如何调度

2016-12-04 13:34:56.136 GCD[3726:150720] 任务1
2016-12-04 13:34:56.137 GCD[3726:150720] 任务5
2016-12-04 13:34:56.137 GCD[3726:150765] 任务2
2016-12-04 13:34:56.142 GCD[3726:150720] 任务3
2016-12-04 13:34:56.143 GCD[3726:150765] 任务4


1最外层分析,首先执行任务1,然后遇到异步线程,不阻塞,直接任务5,由于异步线程有任务2,直接输出

2.这个异步线程是全局并发队列,但是里面又遇到了同步线程,也就是说任务2执行完之后线程阻塞,这个同步线程的任务3是加到mainQueue中的,也就是任务5之后

3.前面已经执行完了任务125或152,那么阻塞的3可以顺利执行,执行完3之后就可以顺利地执行任务4



线程死锁4

dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"任务2"); }); NSLog(@"任务3");});
NSLog(@"任务4");
while (1) {

}
NSLog(@"任务5");
这里会有警告
code will never be executed 埋了隐患,编译器还是还强的,注意看就能避免很多死锁


打印日志:

2016-12-04 13:52:09.597 GCD[3976:163387] 任务1
2016-12-04 13:52:09.597 GCD[3976:163302] 任务4


分析:

1.一开始就是一个异步线程,任务4,死循环和任务5,这里注定了任务5不会被执行,如果其他线程有任务加到主线程中来,那么必定卡死

2.肯定能打印1和4,然后异步线程中遇到同步线程,同步线程的任务是加到mainQueue中的,也就是加到任务5之后,我擦,这肯定炸了,任务5是不会执行的,因此,任务3肯定不会被执行,而且异步线程里面的是同步阻塞的,那么任务3之后的代码肯定也不会执行

3.这里main里面的死循环理论上是不会影响异步线程中的任务1,2,3的,但是任务2是要被加到主队列执行的,那么忧郁FIFO的原理,导致不会执行任务2,那么就死锁了



总结

很多死锁造成的原因第一点是在主线程或者子线程中遇到了一个同步线程,如果这个同步线程把任务加到自己所在线程的同步队列里面就会死锁(mainQueue也是同步队列)

dispatch_sync(来一个同一个同步队列或者mainQueue, <#^(void)block#>)


这种情况下及其容易死锁,千万要小心

能明白就能让上面的线程死锁例子二进行解锁

死锁例子二:

解锁1 新增一个串行队列

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.mkj.serialQueue1", DISPATCH_QUEUE_SERIAL);

NSLog(@"任务1");
dispatch_async(serialQueue, ^{
NSLog(@"任务2");
dispatch_sync(serialQueue1, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");


解锁2 用全局并发队列

dispatch_queue_t serialQueue = dispatch_queue_create("com.mkj.serialQueue", DISPATCH_QUEUE_SERIAL);

NSLog(@"任务1");
dispatch_async(serialQueue, ^{
NSLog(@"任务2");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");


都能正常打印出

15234 或者 12345这个异步的2和5无法确定,所有有几种可能
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐