iOS GCD 线程死锁的疑问

2016-04-09 18:29:54 +08:00
 Dean
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
        NSLog(@"sync1:%@", [NSThread currentThread]);
    });

输出 sync1:<NSThread: 0x7fa731f06030>{number = 1, name = main}

这段代码是在主线程执行的, 在调试这段代码前,一直以为会死锁。我的理解是 dispatch_sync 同步操作会阻塞当前线程即主线程,同时队列将这个任务放到主线程执行(从输出看到),发生了双向阻塞为什么没有死锁。有请高手指证问题。

4237 次点击
所在节点    iDev
17 条回复
zwzmzd
2016-04-09 18:37:53 +08:00
无责任猜测, dispatch_sync 可以认为是“让出当前线程并等待完成”
Dean
2016-04-09 18:54:50 +08:00
@zwzmzd 感觉这个例子让我有点懵了。。。
zwzmzd
2016-04-09 19:11:08 +08:00
参考操作系统中阻塞型调用的实现,例如 socket 中的 recv 。此类调用在没有数据的情况下,相当于将控制权交还给系统调度器,而不是占有线程继续忙等待。

你的代码可以理解为下面的步骤:

1.将 block 注册为一个任务,并加入主线程“待执行”队列
2.注册完毕后,将自己加入主线程“待执行”队列,并让出当前线程(主线程)
3.调度器从主线程“待执行”队列中调度出刚刚注册的 GCD ,于是 NSLog(@"sync1:%@", [NSThread currentThread]);得到了执行。
4.GCD 执行完毕后,让出当前线程(主线程)
5.调度器从主线程“待执行”队列取出外层的任务,发现其等待条件已经得到满足,继续允许执行
Vernsu
2016-04-09 20:16:00 +08:00
你看,这样就死锁了。

dispatch_queue_t queue = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);

dispatch_sync(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"sync1:%@", [NSThread currentThread]);
});
NSLog(@"sync1:%@", [NSThread currentThread]);
});
Vernsu
2016-04-09 20:19:41 +08:00
没有死锁的原因是 block 块中的任务加入了你所创建的串行队列,而不是主队列。所以不存在违背 FIFO 原则的问题。
以上都是我猜的。
zhangchioulin
2016-04-09 20:26:22 +08:00
mark 下班后到家了回复你
LINAICAI
2016-04-09 21:20:35 +08:00
死锁是两个线程之间竞争资源,但都拿不到访问资源的锁时造成的
Dean
2016-04-09 21:31:43 +08:00
自己查了下官方的文档 https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html 其中得出 GCD 出现死锁的结论: dispatch_sync 的当前执行队列与提交 block 执行的目标队列相同时将造成死锁。此处 Block 所在的队列与 dispatch_sync 在队列不同分别是自定义队列与主队列,至于 Block 输出的线程是主线程应该是线程池中的线程重用。
xi_lin
2016-04-09 23:29:52 +08:00
@Dean 推荐一个分析 libdispatch 的系列 http://blog.csdn.net/passerbysrs/article/details/18407959 这篇里提到 dispatch_sync 分发 block 到串行队列时会走到 queue.c 里的_dispatch_barrier_sync_f_slow 方法,里面会试图获取当前 thread 的 semaphore 。所以如果 dispatch_sync 执行时无法获取到信号量,就会一直死锁等下去了。
Alchemistxxd
2016-04-10 00:41:41 +08:00
queue != current Q, 所以 dispatch_sync 只是阻塞 queue ,并不会死锁。死锁的一个典型例子是 dispatch_sync(dispatch_get_main_queue(), ^{ });
codeisjobs
2016-04-10 00:50:14 +08:00
不管主队列还是其他队列,同步函数在手动创建的串行队列中是串行执行,不会发生死锁。只有同步函数在主队列中的时候,同时在主队列调用才会发生死锁。
codeisjobs
2016-04-10 00:57:10 +08:00
主队列中开了一个普通的串行队列来执行同步函数,并不会影响主队列,这是两个不同的队列。你要是在主队列中用主队列执行同步函数,你等我,我等你,才会死锁。
Loker
2016-04-10 21:19:25 +08:00
这样才会死锁
NSLog(@"Task 1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Task 2");
});
NSLog(@"Task 3");
EdwardEan
2016-04-12 14:28:51 +08:00
不知道题主有没有读过 dispatch_sync 方法的这一段注释:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
如果明白了请试着考虑下下面一段代码的输出结果:
EdwardEan
2016-04-12 14:29:43 +08:00
接上一段回复内容:

dispatch_sync(dispatch_get_global_queue(0, 0), ^{

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"First");
}];

[[NSOperationQueue currentQueue] addOperationWithBlock:^{
NSLog(@"Three");
}];

__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"MyNotif" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSLog(@"Receive Notif");
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];

[[NSOperationQueue currentQueue] addOperationWithBlock:^{
NSLog(@"Forth");
}];

[[NSNotificationCenter defaultCenter] postNotificationName:@"MyNotif" object:self];

[[NSOperationQueue currentQueue] addOperationWithBlock:^{
NSLog(@"Five");
}];
});
lee0100317
2016-04-13 17:29:05 +08:00
请注意区分 Thread 和 dispatch_queue , dispatch_sync 阻塞的是 dispatch_queue ,而不是 Thread , Thread 只是 dispatch_queue 完成调度的载体而已。 dispatch_queue 存在的意义就是希望可以不再涉及 Thread 的内容,以及彻底抛弃锁。
Biscuits
2019-05-30 10:31:08 +08:00
@lee0100317 的确是这样, 他没搞清楚 dispatch_queue 和执行的 Thread 其实是分开的.

先回答为什么没有死锁问题 dispatch_sync 文档的 discussion 里面有这么一句 "Calling this function and targeting the current queue results in deadlock." 所以死锁问题有正确答案了. https://developer.apple.com/documentation/dispatch/1452870-dispatch_sync

然后解释为什么是主线程 还是👆的文档里面的 "As a performance optimization, this function executes blocks on the current thread whenever possible, with one obvious exception."

现在有 新建的 queue 和 main dispatch queue, 都是任务, Main_Thread 是(执行)资源. 在 Main_thread 调用 dispatch_sync, 把 block 任务加入 新 queue, 然后按照"this function executes blocks on the current thread whenever possible" 新 queue 拥有了主线程的执行资源, 进行执行, 然后返回继续 main dispatch queue .
所以 main dispatch queue 是持有了什么东西造成了死锁呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/269799

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX