iOS案例 - GCD 死锁

首先来回忆几个概念:

  1. 任务:需要执行的操作;
  2. 队列:储存任务的数据结构;
  3. 同步:只能开启一个子线程执行队列;
  4. 异步:可开启多个子线程执行队列;
  5. 串行:同一时间只能执行一个任务;
  6. 并行:同一时间可执行多个任务;

假设你已经了解上面几个概念,那么我们开始通过几个案例来了解一下死锁:

案例一

1
2
3
4
5
6
7
8
9
10
func deadLockOne() {
print("1")
let mainQueue = dispatch_get_main_queue()
dispatch_sync(mainQueue) {
print("2")
}
print("3")
}

打印结果:

1
1

分析:

  1. dispatch_get_main_queue 表示运行在主线程的主队列;
  2. dispatch_sync 表示是一个同步线程;
  3. 2 是同步线程的主队列的任务;

首先执行1 ,是没有问题的,只是接下来,程序遇到了同步线程 dispatch_sync ,那么它会进入队列,等待2 执行完,然后执行3。但这是队列,有任务来,当然会将任务加到队尾部,然后遵循先入先出的原则执行任务,那么,2 就会被加到最后,3 排在 2 的前面。

使用 dispatch_sync 执行 mainQueue 时, mainQueue 存放着两个任务 1、3 ,使用 block 加入了 2 以后,mainQueue 变成 1、3、2 ,但在执行上,是 1、2、3 ,所以执行队列到 2 时,2 需要等 3 先执行完,但是因为 dispatch_sync 是同步函数,不执行完 2 ,是不会执行 3 的,所以 23 就进入了互相等的状态,死锁发生。

图解:

案例二

1
2
3
4
5
6
7
8
9
10
func deadLockTwo() {
print("1")
let concurrentQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_sync(concurrentQueue) {
print("2")
}
print("3")
}

打印结果:

1
2
3
4
5
1
2
<NSThread: 0x7c8627c0>{number = 1, name = main}
3
<NSThread: 0x7c8627c0>{number = 1, name = main}

分析:

  1. dispatch_get_global_queue 表示是一个全局并行队列;
  2. dispatch_sync 表示是一个同步线程;
  3. 2 是同步线程的全局并行队列的任务;

首先执行 1 ,接下来遇到一个同步线程,程序会进入等待,等待任务 2 完成以后,才会继续执行任务 3 ,从 dispatch_get_global_queue 可以看出,2 被加入一个全局并行队列中,当 2 执行以后,不等待结果,返回到主队列,继续执行 3

图解:

案例三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func deadLockThird() {
let queue = dispatch_queue_create("tianziyao.space", DISPATCH_QUEUE_SERIAL)
print("1 线程\(NSThread.currentThread())")
dispatch_async(queue) {
print("2 线程\(NSThread.currentThread())")
dispatch_sync(queue, {
print("3 线程\(NSThread.currentThread())")
})
print("4 线程\(NSThread.currentThread())")
}
print("5 线程\(NSThread.currentThread())")
}

打印结果:

1
2
3
1 线程<NSThread: 0x7967a6c0>{number = 1, name = main}
5 线程<NSThread: 0x7967a6c0>{number = 1, name = main}
2 线程<NSThread: 0x7aa922e0>{number = 3, name = (null)}

分析:

这个案例没有使用系统提供的串行或并行队列,而是自己通过 dispatch_queue_creat 创建了DISPATCH_QUEUE_SERIAL 的串行队列。

  1. 执行 1
  2. 遇到异步线程,将 2、同步线程、4 加入串行队列,因为是异步线程,所以主线程中的 5 不必等待异步线程内的任务执行完毕;
  3. 因为 5 不需要等待,所以异步线程里的 2,和主线程中的 5 ,执行顺序不能确定;
  4. 2 执行完以后,遇到同步线程,这时将 3 加入串行队列;
  5. 又因为 43 先加入串行队列,所以 4 执行完以后 3 才会执行,但是 同步线程 要执行完 3 才能返回,所以造成死锁。

图解:

QQ20160723-1

案例四

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func deadLockFour() {
let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let mainQueue = dispatch_get_main_queue()
print("1")
dispatch_async(globalQueue) {
print("2 线程\(NSThread.currentThread())")
dispatch_sync(mainQueue, {
print("3 线程\(NSThread.currentThread())")
})
print("4 线程\(NSThread.currentThread())")
}
print("5 线程\(NSThread.currentThread())")
}

打印结果:

1
2
3
4
5
1
5 线程<NSThread: 0x79e31490>{number = 1, name = main}
2 线程<NSThread: 0x7b33ae60>{number = 3, name = (null)}
3 线程<NSThread: 0x79e31490>{number = 1, name = main}
4 线程<NSThread: 0x7b33ae60>{number = 3, name = (null)}

分析:

首先,主线程的队列是 mainQueue ,任务是:1、异步线程、5

异步线程的队列是 globalQueue ,任务是:2、同步线程、4

  1. 主线程中执行 1 ,然后遇到异步函数 ,将 2、同步线程、4 加入并行队列;
  2. 开线程,因为 2 在异步线程,所以 25 的执行顺序随机,但在同一级运行;
  3. 异步线程中遇到同步线程,执行主队列,将 3 加入到主队列,这时 3 在 5的后面
  4. 这时 5 ,已经执行完,所以 3 可以执行,然后执行 4

从以上分析看,25 的输出顺序不确定,但是 5 一定在 3 前面,3 一定在 4 前面;

图解:

QQ20160723-2

案例五

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func deadLockFive() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
print("1 线程\(NSThread.currentThread())")
dispatch_sync(dispatch_get_main_queue(), {
print("2 线程\(NSThread.currentThread())")
})
print("3 线程\(NSThread.currentThread())")
}
print("4 线程\(NSThread.currentThread())")
while true {
}
print("5 线程\(NSThread.currentThread())")
}

打印结果:

1
2
1 线程<NSThread: 0x7c1b5250>{number = 2, name = (null)}
4 线程<NSThread: 0x7b77a510>{number = 1, name = main}

分析:

首先来看下,加进了哪些任务:

mainQueue 里面有 异步线程、4、死循环、5globalQueue 里面有 1、同步线程、3

  1. 异步线程,所以 4 不用等,14 的顺序随机;
  2. 主线程遇到死循环,所以 5 不会执行,异步线程中遇到同步线程执行主队列,25 的后面,因为 5 不会执行,所以 2 是死锁,所以 3 也不会执行。

最终结果是 14,执行顺序随机。

图解:

QQ20160723-3