GCD 串行队列调度异步和同步

Posted

技术标签:

【中文标题】GCD 串行队列调度异步和同步【英文标题】:GCD Serial Queue dispatch async and sync 【发布时间】:2021-10-24 20:23:55 【问题描述】:

我对 GCD 有一些疑问。

代码 sn-p 1

serialQ.sync 
    print(1)
    serialQ.async 
        print(2)
    
    serialQ.async 
        print(3)
    

代码 sn-p 2

serialQ.async 
    print(1)
    serialQ.async 
        print(2)
    
    serialQ.sync 
        print(3)
    
 

我在操场上运行了它们,发现 Code sn-p 2 产生了死锁,而 Code sn-p 1 运行良好。我已经阅读了很多关于 GCD 的内容,并开始研究这些概念。任何人都可以提供相同的详细解释吗? PS : serialQ 是一个串行队列

据我了解,

串行队列 - 一次只生成一个线程,一旦该线程被释放,它就会被占用或空闲来执行其他任务

串行队列调度同步 - 阻塞调度串行队列的调用者线程并在该线程上执行任务。

串行队列调度异步 - 不会阻塞调用者线程,事实上它在不同的线程上运行并保持调用者 线程正在运行。

但对于上述查询,我​​无法得到正确的解释。

【问题讨论】:

【参考方案1】:

您在已经在同一个队列上执行的块内调用sync。这总是会导致死锁。 sync 相当于说“现在执行并等待它返回”。由于您已经在该队列上执行,因此该队列永远无法用于执行 sync 块。听起来您正在寻找递归锁,但这不是队列的工作方式。一般来说,它也可以说是一种反模式。我在这个答案中对此进行了更多讨论:How to implement a reentrant locking mechanism in objective-c through GCD?

编辑:回来补充一些关于你的“理解”的想法:

串行队列 - 一次只生成一个线程,一旦该线程被释放,它就会被占用或空闲来执行其他任务

串行队列不会“生成”一个线程。队列和线程是不同的东西,具有不同的语义。串行队列需要一个线程来执行工作项,但串行队列和线程之间没有一对一的关系。线程是相对“重”的资源,队列是相对“轻”的资源。单个串行队列在其生命周期内可以在多个线程上执行工作项(尽管同时不会超过一个线程)。 GCD 维护用于执行工作项的线程池,但这是一个实现细节,没有必要了解它是如何实现的以便正确使用队列。

串行队列调度同步 - 阻塞调度串行队列的调用者线程并在该线程上执行任务。

队列(串行或并发)未“分派”(同步或其他方式)。一个工作项被排入队列。该工作项随后将由任意线程执行,很可能包括调用线程。保证一次只有一个排入给定 serial 队列的工作项将被执行(在任何线程上)。

串行队列调度异步 - 不会阻塞 ~caller~ 入队线程,实际上它在不同的线程上运行并保持调用者线程运行。 (为了便于阅读,进行了一些小改动)

这很接近,但不太准确。确实,使用async 将工作项排入串行队列不会阻塞入队线程。工作项不一定是由与入队线程不同的线程执行的,尽管在常见情况下,通常是这种情况。

这里要知道的是,syncasync 之间的区别严格限于 enqueueing 线程的行为,并且对哪个线程没有(保证)影响或影响。工作项被执行。如果您使用sync 将工作项入队,入队线程将等待(可能永远,在您在此处概述的特定情况下)等待工作项完成,而如果您使用async 将工作项入队,入队线程将继续正在执行。

【讨论】:

【参考方案2】:

正如您所指出的,sync 调用会阻塞当前线程,直到该块运行为止。因此,当您将 sync 发送到您当前所在的同一个串行队列时,您正在阻塞队列,等待一个块在您刚刚阻塞的同一队列上运行,从而导致死锁。

如果你真的想在当前队列上同步运行一些东西,根本不要用sync调度它,直接运行它。例如:

serialQ.async 
    print(1)
    serialQ.async 
        print(2)
    
    // serialQ.sync   // don't dispatch synchronously to the current serial queue
        print(3)
    // 
 

或者异步调度。例如,

serialQ.async 
    print(1)
    serialQ.async 
        print(2)
    
    serialQ.async 
        print(3)
    
 

或者使用并发队列(在这种情况下,您必须小心确保没有线程爆炸,这也可能导致死锁)。例如,

let concurrentQ = DispatchQueue(label: "...", attributes: .concurrent)

concurrentQ.async 
    print(1)
    concurrentQ.async 
        print(2)
    
    concurrentQ.sync 
        print(3)
    
 

【讨论】:

以上是关于GCD 串行队列调度异步和同步的主要内容,如果未能解决你的问题,请参考以下文章

在 GCD 中,串行队列通过异步操作 Swift 同步

iOS多线程——同步异步串行并行

GCD使用 串行并行队列 与 同步异步执行的各种组合 及要点分析

同步,异步,串行队列,并发队列,全局队列,主队列等概念的总结

IOS多线程知识总结/队列概念/GCD/串行/并行/同步/异步

iOS多线程知识总结/队列概念/GCD/串行/并行/同步/异步