死锁及线程与队列之间的等待关系
- 死锁及线程与队列之间的等待关系
- 案例一
- 案例二
- 案例三
- 案例四
- 案例五
- 结语
死锁及线程与队列之间的等待关系
我想要补充一下我之前GCD学习中没能理解清楚的死锁及线程与队列之间的等待关系,因为在看锁的博客时,有人给出了一个案例来讲解死锁问题 ;
具体可以看[iOS] 谈谈iOS多线程的锁
在讲解案例前,我想先简单描述一下线程和队列之间的关系 ;
我们可以简单的认为,线程是执行任务的工人 ;队列是管理任务的地方 ;举一个简单的例子 :
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
我们执行上面这段代码 ;这里只有主线程这一名工人,这时任务一,dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“2”);
});和任务三按顺序添加到主队列中,但执行到dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“2”); // 任务2
});时,这时会把块中的任务二添加到主对列中并在任务二执行完之前阻塞主线程,
这一过程如图:
这时任务3等待任务2执行完解锁主线程,任务2等待任务3进入主线程执行;两者互相等待,从而造成死锁 ;
如果:
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
这里只有主线程这一名工人,这里只有主线程这一名工人,这时任务一,dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“2”);
});和任务三按顺序添加到主队列中,但执行到dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“2”); // 任务2
});时,这时会把块中的任务二添加到主对列中,但在任务二执行完之前不会阻塞主线程,所以如图:
这个时候我们再来想想串行队列和并行队列以及同步操作和异步操作间的区别 ;
- 在串行队列中,任务是按照顺序逐个执行的。每个任务完成后,才会从队列中取出下一个任务并执行。在串行队列中,只有一个线程用于执行任务,因此后续的任务必须等待前面的任务完成后才能执行。
- 当一个任务在串行队列中执行时,它会占用执行该队列的线程。其他任务必须等待该线程空闲下来,才能从队列中取出并执行。所以,在串行队列中,任务是按照顺序执行的,每个任务都需要等待前面的任务完成,即等待执行该任务的线程空闲下来。
- 与之相反,在并行队列中,多个任务可以并发执行,不需要等待前面的任务完成。并行队列可以拥有多个线程用于执行任务,因此多个任务可以同时从队列中取出并执行,而不必等待其他任务或线程空闲。
串行和并行决定了队列内任务等待关系 ;
- 同步操作是指在当前线程上执行任务,并且会阻塞当前线程,直到任务完成。当执行同步操作时,代码会按顺序执行,并等待每个操作完成,然后再继续执行下一个操作。同步操作会阻塞当前线程的执行,直到操作完成,因此在同步操作执行期间,当前线程无法做其他事情。
- 异步操作是指在其他线程上执行任务,不会阻塞当前线程的执行。当执行异步操作时,代码会立即返回,并继续执行后续代码,而不需要等待操作完成。异步操作允许在后台执行任务,提高了程序的响应性能。
同步和异步决定了在线程中执行任务的方式 ;
顺便提醒一下,主线程中默认执行任务都是同步的 ;
串行队列,并行队列,同步,异步简单图示:
这时再结合我前面讲的理解一下其中的关系
案例一
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
执行后:
这里是一个非常典型的死锁 ;上面讲过了这里就不解释了 ;
案例二
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
这里可以看到,主线程完成任务一后,将任务二加入全局队列后阻塞主线程,等待任务二完成后,解锁主线程并完成任务三 ;
案例三
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
主队列中三个任务,执行完任务一后,异步操作将代码块中的三个同步任务插入自定义的串行队列中,这时主线程和线程一都可以执行任务,所以任务二和任务五先后顺序不一定,当queue中取到dispatch。。。。sync时,会阻塞线程一的同时,将代码块中的任务三加到queue尾部,于是任务四和任务三互相等待,造成死锁 ;
案例四
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
下面就不写题解了,和上面差不多 ;
主队列中三个任务,执行完任务一后,异步操作将代码块中的三个同步任务插入自定义的串行队列中,这时主线程和线程一都可以执行任务,所以任务二和任务五先后顺序不一定,当queue中取到dispatch。。。。sync时,会阻塞线程一的同时,将代码块中的任务三加到主队列尾部,这时任务四等待主线程完成任务三后完成
案例五
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
结语
上面这几个例子可以很好的帮助理解线程队列以及任务同步和异步之间的关系,不必去死记这几这者间排列组合的结果 ;