为什么两个不同的串行队列在 swift 中产生死锁?

Why two different serial queue creating deadlock in swift?

我有一个自定义串行队列,在这个队列中同步调用主队列。它正在制造僵局。根据我的理解,两者都是独立的队列,因此它应该可以工作,并且(第 3 步和第 5 步)同步块都应该执行。谁能解释为什么会产生死锁?下面是我的游乐场代码。

func serialQueueTest() {
    let customSerialQueue = DispatchQueue(label: "com.test.dipak")
    
    print("Step 1")
    customSerialQueue.async {
        print("Step 2")
       
        DispatchQueue.main.sync {
            print("Step 3: Inside main sync queue")
        }
    }
    
    print("Step 4")
    customSerialQueue.sync {
        print("Step 5: Inside Custom Serial Queue sync queue")
    }
}

您正在阻止 main。

在您从 main 调用的第 4 步,main 将块提交到队列并等待它完成。但是那时你已经提交了第一个块(第 1 步),它正在等待 main 释放。

编辑

请注意,CSQ 并没有阻塞尝试执行您提交的两个块,而是 CSQ 和 main 正在阻塞等待彼此完成。我可以很容易地说明这一点 if 队列上有一个 isBusy 函数,但既然没有,让我们假装有,看看下面的代码。

    func serialQueueTest() {
        let customSerialQueue = DispatchQueue(label: "com.test.dipak")

        print("Step 1")
        customSerialQueue.async {
            print("Step 2")

            // Previously
            //    DispatchQueue.main.sync { AAA }
            // same as below pseudo code
            while ( main.isBusy )
            {
                wait ... *without* releasing control
            }
            now, on main, do AAA and then proceed

            print ( "****" )
            print ( "CSQ will now wait for main to finish what it is doing ..." )
            print ( "But note, it does not release control or do something else," )
            print ( "it *blocks* until main is finished. So it deadlocks." )
        }

        print("Step 4")

        // Previously
        //    customSerialQueue.sync BBB
        // replaced with ...
        while ( csq.isBusy )
        {
            wait ... *without* releasing control
        }
        now, on csq, do BBB then proceed

        print ( "****" )
        print ( "Main will now wait for csq to finish what it is doing ..." )
        print ( "But note, it does not release control or do something else," )
        print ( "it *blocks* until csq is finished. So it deadlocks." )
    }

即使我只向 CSQ 提交了 一个 块,这也会被块。

要打破僵局,您可以例如当您等待释放控制时(在这种情况下,您可以调用异步而不是同步)或使用不同类型的锁或以不同的方式等待另一个完成。

编辑 2

让我把它归结为本质。

// This runs on main
            // This runs on csq
csq.async { main.sync // csq now waits on main to free up }
csq.sync              // main now waits on csq to free up

                      // and you have deadlock

为了扩展 Tushar Sharma 和 Dipak 的回答,我将按照代码的执行顺序逐步浏览代码。

// == Scheduling a work item from the main queue ==

// Create a new serial queue
let customSerialQueue = DispatchQueue(label: "com.test.dipak")

// Create a work item and append it to customSerialQueue.
// Don't think of this being "in parallel." These are not threads. They're
// queues. It will run the next time customSerialQueue is scheduled. That might
// be immediately (if there's an available core), and that might be in the
// arbitrarily distant future. It doesn't matter what's in this work item. It's
// just "some work to do."
customSerialQueue.async { ... }

// On main queue still
print("Step 4")

// Create a work item, append it to customSerialQueue, and wait for it to
// complete. As before, it doesn't matter what's in this work item. It's just
// stuck onto the end of customSerialQueue and will execute when it gets to the
// front of the queue and the queue is scheduled. Currently it's 2nd in line
// after the Step 2 work item.
customSerialQueue.sync { ... }

此时,main 必须 yield (block)。它无法进步并完成 当前工作项(运行ning serialQueueTest)直到第 5 步工作项完成。

由于现在什么都没有 运行ning,所以 customSerialQueue 上的第一个块可以 运行。

// == Scheduling a work item from customSerialQueue ==
print("Step 2")

// Create a block, append it the main queue, and wait for it to complete.
DispatchQueue.main.sync { ... }

和以前一样,customSerialQueue 必须屈服(阻塞)。在第 3 步工作项完成之前,它无法继续并完成当前工作项(运行ning 第 2 步)。在 main 完成当前 运行ning 的工作项之前,无法安排第 3 步工作项(main 是一个串行队列)。

此时,main 被阻塞等待“第 5 步”块完成,customSerialQueue 被阻塞等待“第 3 步”完成。这是典型的死锁,任务都无法进行。

None 多核存在下的上述变化。 GCD 队列是关于并发性的,而不是并行性(和 concurrency is not parallelism)。它们与运行“同时”发生的事情无关。它们是关于安排工作项目的。因此,您应该首先对它们进行推理,就好像它们 运行 在单个内核上运行一样。然后,您可以添加有关如果同时有两个工作项 运行 会发生什么的问题。但是这个问题并没有改变依赖关系的基本问题。