死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
发生死锁的情况 一般是两个对象的锁相互等待造成的。
那么为什么会产生死锁呢?有3个原因:第一,因为系统资源不足;第二,进程运行推进的顺序不合适;第三,资源分配不当。
产生死锁的条件有四个:
l 互斥条件:所谓互斥就是进程在某一时间内独占资源。
l 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
l 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
l 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在相互等待,造成了无法执行的情况。
避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C
代码分析:
- (void)viewDidLoad {
//dispatch_sync(dispatch_get_main_queue(), ^{
//NSLog(@3);
//死锁原因
//1:dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁
//2:dispatch_sync是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block,所以就会发生死锁。
//});
//dispatch_async(dispatch_get_global_queue(), ^{
//async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回
NSLog(@2);//不会造成死锁;
});
}
分析这段代码:view DidLoad 在主线程中,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到后面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却
在等待sync返回,去执行后续工作,从而造成死锁。
2:
dispatch_sync 和 dispatch_async 区别:
dispatch_async(queue,block) async 异步队列,dispatch_async
函数会立即返回, block会在后台异步执行。
dispatch_sync(queue,block) sync 同步队列,dispatch_sync
函数不会立即返回,即阻塞当前线程,等待 block同步执行完成。
接下来是同步与异步线程的创建:
1
2
3
|
dispatch_sync
(
.
.
.
,
^
(
block
)
)
// 同步线程
dispatch_async
(
.
.
.
,
^
(
block
)
)
// 异步线程
|
假设你已经基本了解了上面提到的知识,接下来进入案例讲解阶段。
1
2
3
4
5
6
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
|
结果,控制台输出:
1
2
|
1
|
分析:
首先执行任务1,这是肯定没问题的,只是接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是队列,有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务。那么,现在任务2就会被加到最后,任务3排在了任务2前面,问题来了:
任务3要等任务2执行完才能执行,任务2由排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面。【既然这样,那干脆就卡在这里吧】这就是死锁。
1
2
3
4
5
6
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_global_queue
(
DISPATCH_QUEUE_PRIORITY_HIGH
,
0
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
|
结果,控制台输出:
1
2
3
4
|
1
2
3
|
分析:
首先执行任务1,接下来会遇到一个同步线程,程序会进入等待。等待任务2执行完成以后,才能继续执行任务3。从dispatch_get_global_queue可以看出,任务2被加入到了全局的并行队列中,当并行队列执行完任务2以后,返回到主队列,继续执行任务3。
1
2
3
4
5
6
7
8
9
10
11
|
dispatch_queue_t
queue
=
dispatch_queue_create
(
"com.demo.serialQueue"
,
DISPATCH_QUEUE_SERIAL
)
;
NSLog
(
@"1"
)
;
// 任务1
dispatch_async
(
queue
,
^
{
NSLog
(
@"2"
)
;
// 任务2
dispatch_sync
(
queue
,
^
{
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
}
)
;
NSLog
(
@"5"
)
;
// 任务5
|
结果,控制台输出:
1
2
3
4
5
|
1
5
2
// 5和2的顺序不一定
|
分析:
这个案例没有使用系统提供的串行或并行队列,而是自己通过dispatch_queue_create函数创建了一个DISPATCH_QUEUE_SERIAL的串行队列。
1
2
3
4
5
6
7
8
9
10
|
NSLog
(
@"1"
)
;
// 任务1
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
}
)
;
NSLog
(
@"5"
)
;
// 任务5
|
结果,控制台输出:
1
2
3
4
5
6
7
|
1
2
5
3
4
// 5和2的顺序不一定
|
分析:
首先,将【任务1、异步线程、任务5】加入Main Queue中,异步线程中的任务是:【任务2、同步线程、任务4】。
所以,先执行任务1,然后将异步线程中的任务加入到Global Queue中,因为异步线程,所以任务5不用等待,结果就是2和5的输出顺序不一定。
然后再看异步线程中的任务执行顺序。任务2执行完以后,遇到同步线程。将同步线程中的任务加入到Main Queue中,这时加入的任务3在任务5的后面。
当任务3执行完以后,没有了阻塞,程序继续执行任务4。
从以上的分析来看,得到的几个结果:1最先执行;2和5顺序不一定;4一定在3后面。
1
2
3
4
5
6
7
8
9
10
11
12
|
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
)
,
^
{
NSLog
(
@"1"
)
;
// 任务1
dispatch_sync
(
dispatch_get_main_queue
(
)
,
^
{
NSLog
(
@"2"
)
;
// 任务2
}
)
;
NSLog
(
@"3"
)
;
// 任务3
}
)
;
NSLog
(
@"4"
)
;
// 任务4
while
(
1
)
{
}
NSLog
(
@"5"
)
;
// 任务5
|
1
|
结果,控制台输出:
|
1
2
3
4
|
1
4
// 1和4的顺序不一定
|
分析:
和上面几个案例的分析类似,先来看看都有哪些任务加入了Main Queue:【异步线程、任务4、死循环、任务5】。
在加入到Global Queue异步线程中的任务有:【任务1、同步线程、任务3】。
第一个就是异步线程,任务4不用等待,所以结果任务1和任务4顺序不一定。
任务4完成后,程序进入死循环,Main Queue阻塞。但是加入到Global Queue的异步线程不受影响,继续执行任务1后面的同步线程。
同步线程中,将任务2加入到了主线程,并且,任务3等待任务2完成以后才能执行。这时的主线程,已经被死循环阻塞了。所以任务2无法执行,当然任务3也无法执行,在死循环后的任务5也不会执行。
最终,只能得到1和4顺序不定的结果。