V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
Dean
V2EX  ›  iDev

iOS GCD 线程死锁的疑问

  •  
  •   Dean · 2016-04-09 18:29:54 +08:00 · 4427 次点击
    这是一个创建于 3152 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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 同步操作会阻塞当前线程即主线程,同时队列将这个任务放到主线程执行(从输出看到),发生了双向阻塞为什么没有死锁。有请高手指证问题。

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

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

    1.将 block 注册为一个任务,并加入主线程“待执行”队列
    2.注册完毕后,将自己加入主线程“待执行”队列,并让出当前线程(主线程)
    3.调度器从主线程“待执行”队列中调度出刚刚注册的 GCD ,于是 NSLog(@"sync1:%@", [NSThread currentThread]);得到了执行。
    4.GCD 执行完毕后,让出当前线程(主线程)
    5.调度器从主线程“待执行”队列取出外层的任务,发现其等待条件已经得到满足,继续允许执行
    Vernsu
        4
    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
        5
    Vernsu  
       2016-04-09 20:19:41 +08:00
    没有死锁的原因是 block 块中的任务加入了你所创建的串行队列,而不是主队列。所以不存在违背 FIFO 原则的问题。
    以上都是我猜的。
    zhangchioulin
        6
    zhangchioulin  
       2016-04-09 20:26:22 +08:00
    mark 下班后到家了回复你
    LINAICAI
        7
    LINAICAI  
       2016-04-09 21:20:35 +08:00
    死锁是两个线程之间竞争资源,但都拿不到访问资源的锁时造成的
    Dean
        8
    Dean  
    OP
       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
        9
    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
        10
    Alchemistxxd  
       2016-04-10 00:41:41 +08:00
    queue != current Q, 所以 dispatch_sync 只是阻塞 queue ,并不会死锁。死锁的一个典型例子是 dispatch_sync(dispatch_get_main_queue(), ^{ });
    codeisjobs
        11
    codeisjobs  
       2016-04-10 00:50:14 +08:00 via iPhone
    不管主队列还是其他队列,同步函数在手动创建的串行队列中是串行执行,不会发生死锁。只有同步函数在主队列中的时候,同时在主队列调用才会发生死锁。
    codeisjobs
        12
    codeisjobs  
       2016-04-10 00:57:10 +08:00 via iPhone   ❤️ 1
    主队列中开了一个普通的串行队列来执行同步函数,并不会影响主队列,这是两个不同的队列。你要是在主队列中用主队列执行同步函数,你等我,我等你,才会死锁。
    Loker
        13
    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
        14
    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
        15
    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
        16
    lee0100317  
       2016-04-13 17:29:05 +08:00
    请注意区分 Thread 和 dispatch_queue , dispatch_sync 阻塞的是 dispatch_queue ,而不是 Thread , Thread 只是 dispatch_queue 完成调度的载体而已。 dispatch_queue 存在的意义就是希望可以不再涉及 Thread 的内容,以及彻底抛弃锁。
    Biscuits
        17
    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 是持有了什么东西造成了死锁呢?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1029 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 22:32 · PVG 06:32 · LAX 14:32 · JFK 17:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.