死锁
1.定义
所谓死锁,通常指的是两个线程T1和T2都被卡住,并等待对方完成某些操作,T1等待T2完成,T2等待T1完成,于是大家都完成不了,就造成了死锁(deadLock)
2.产生死锁的条件
产生死锁对的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放
(3)不剥夺条件:进程已得到的资源,在未使用完成之前,不能强行剥夺
(4)循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,只要有一个不满足,就不会死锁
3.图示
image.png死锁代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"这里死锁了");
});
}
return 0;
}
//执行这个dispatch_get_main_queue队列的是主线程,执行了dispatch_sync函数后,将block添加到了main_queue中,同时调用这个dispatch_sync函数的线程被阻塞,等待block完成,而执行主线程队列任务的线程正是主线程,此时他处于阻塞状态,所以block永远不会被执行,因为主线程一直处于阻塞状态,因此代码运行之后,并非卡在了block无法返回,而是根本无法执行到这个block
基本概念
1.同步&异步
1.1、同步
同步执行:比如这里的dispatch_sync,这个函数会把一block加入到指定的队列,而且会一直等到执行完block,这个函数才会返回,因此在执行完blcok之前,调用dispatch_sync的线程一直处于阻塞状态
1.2、异步
异步执行:dispatch_async,这个函数会把一个block添加到指定的队列中,但是和同步执行不一样的是,这个函数把block加入队列后不等block的执行就立刻返回。
1.3、关于GCD函数的强调
dispatch_async 和dispatch_sync 他们的作用是将任务 (block) 添加进指定的队列中。并根据是否为sync决定调用该函数的线程是否需要阻塞
注意:这里调用该线程的函数并不执行 参数中指定的任务(block块),任务的执行者是GCD分配给任务所在队列的线程。
结论:调用dispatch_async和dispatch_sync线程,并不一定是任务(block块)的执行者。
2、串行&并发
2.1、串行队列
串行队列:比如dispatch_get_main_queue。队列中所有任务,一定按照FiFo(先进先出)执行。不仅如此,还可以保证在执行某个任务时,在他前面进入队列的所有任务肯定执行完了对于每一个不同的串行队列,系统都会为这个队列建立唯一的线程来执行代码
2.2、并发队列
比如dispatch_get_global_queue。这个队列中的任务也是按照先进先出的顺序执行,但是他们执行结束的时间是不确定的,取决于每个任务的耗时,并发队列中的任务:GCD会动态分配多条线程来执行,具体几条线程取决于当前内存使用状况,线程池中线程数等因素。
3、总结
3.1、异步执行block肯定不会发生死锁,回顾一下导致死锁的原因,是因为主线程在执行dispatch_sync,这是一个同步方法,block执行完成之前都不会返回。而async是异步的执行,是立刻返回的,因此不会阻塞主线程,双向的阻塞不成立,只是主线程处理block时候阻塞,这不会引起死锁
3.2、同步的向并发队列添加block不会导致死锁
原因:由于之前在串行队列中添加了block,block一直要等待前面的任务处理完成才会执行,从而造成了死锁。如果采用同步的向并发队列中添加block,首先需要明确一点,就是同步方法是不会再开线程的,也就是说当前的调用线程会立即去执行block,直到block执行完成后才会继续向下执行。因为是并发队列,队列中的下一个任务执行不需要等待上一个任务的完成,所以,即使添加到当前调用任务的队列也不会造成死锁,当前线程会立即执行新添加的任务,然后返回,并继续向下执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"11111====current thread :%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"222====current thread :%@",[NSThread currentThread]);
});
NSLog(@"333333333");
});
结果:
[10324:5277752] 11111====current thread :<NSThread: 0x60400027f3c0>{number = 3, name = (null)}
[10324:5277752] 222====current thread :<NSThread: 0x60400027f3c0>{number = 3, name = (null)}
[10324:5277752] 333333333
我们可以看到,并发队列中:同一条线程在上一个任务没有完成的情况下,可以去执行下一个任务。因为并发队列中下一个任务的执行不需要上一个任务执行完成。
3.2、如果同步向另一个串行队列添加方法,并不一定会造成死锁。
dispatch_queue_t queue = dispatch_queue_create("simple",nil);
dispatch_sync(queue,^(void){
nslog(@"哈哈哈哈")
});
因为:simple这个队列的执行线程是主线程,同步方法不会开辟新线程,但这个是将任务添加到了simple这个队列中,所以主线程会立即来执行这个队列中的任务,执行完后就会返回,因此主线程不会继续被阻塞,所以不会死锁
造成死锁的唯一原因:
在某一串行队列中,同步的向这个串行队列添加block
因为队列是可以嵌套的,比如在A队列(串行)添加一个任务a,在a这个任务中像B队列(串行)添加任务b,在b这个任务中又向A队列添加任务,这间接满足了“在某一个串行队列中,同步的向这个队列添加block”。但是我们好像每一次都没有直接向相同的队列添加block
所以:判断是否发生死锁的最好方法就是看有没有在串行队列(包括主队列)中向这个队列添加任务
我们使用同步的方法编程,往往是要求保证人物之间的执行顺序是完全正确的。且不说GCD提供了很多强大的功能来满足这个需求,向串行队列在同步的添加任务本身就是不合理的,毕竟队列已经是串行了,直接异步添加就可以了
dispatch_queue_t queue = dispatch_queue_create("test.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_apply(3, queue, ^(size_t i) {
NSLog(@"apply loop: %zu", i);
//再来一个dispatch_apply!死锁!
dispatch_apply(3, queue, ^(size_t j) {
NSLog(@"apply loop inside %zu", j);
});
});
dsipatch_apply嵌套使用也会死锁
网友评论