Swift中使用main.sync时引起DispatchQueue崩溃

22
请解释为什么我会遇到这个崩溃?

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

在这里

DispatchQueue.main.sync { print("sync") }

这是我的代码。

    override func viewDidLoad() {
    super.viewDidLoad()


    print("Start")
    DispatchQueue.main.async {
        print("async")

    }
    DispatchQueue.main.sync {
        print("sync")
    }
    print("Finish")
}
4个回答

30

永远不要在主队列上调用同步函数

如果您在主队列上调用同步函数,它将会阻塞队列,因为队列将等待任务完成,但任务永远无法完成,因为由于队列已经被阻塞,任务甚至无法开始。这被称为死锁

两个(或有时更多)项目 - 在大多数情况下是线程 - 如果它们都陷入等待彼此完成或执行另一个操作中,则被称为死锁。第一项无法完成,因为它正在等待第二项完成。但第二项无法完成,因为它正在等待第一项完成。

但是您需要小心。想象一下如果您调用sync并且目标是您已经运行的当前队列。这将导致死锁状态。

使用sync来跟踪使用调度障碍的工作,或者当您需要等待操作完成才能使用闭包处理的数据时使用sync。

何时使用sync?

当我们需要等待任务完成时。例如 当我们确保某个函数/方法不被重复调用时。例如 我们进行同步并尝试防止它被重复调用,直到它完全完成。当您需要等待在不同队列上完成某些操作,然后才能继续在当前队列上工作。

同步与异步

使用GCD,您可以将任务分派为同步或异步。

同步函数在任务完成后将控制权返回给调用方。

异步函数立即返回,命令执行任务但不等待它。因此,异步函数不会阻塞当前执行线程进入下一个函数。


13

@sankalap,Dispatch.main是一个串行队列,只有一个线程来执行所有操作。如果我们在这个队列上调用"sync",它将阻塞当前线程上运行的所有其他操作,并尝试执行sync代码块中你所编写的内容。这会导致“死锁”。


1
死锁并不是崩溃。 - rmaddy
@rmaddy 你是对的。我也同意死锁不是崩溃。 - Rohi
10
这是一个死锁。程序崩溃是因为GCD检测到这种特定形式的死锁并中止了程序。如果你能重现这个问题并查看调试器对_dispatch_sync_wait的反汇编,你会看到消息“BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread”(LIBDISPATCH客户端BUG:在当前线程上已拥有队列的情况下调用了dispatch_sync)。 - rob mayoff

9
根据苹果在执行 dispatch_sync 函数时的文档,如果在当前队列上执行该函数将导致代码崩溃:

调用此函数并针对当前队列会导致死锁。


3
死锁和崩溃不同,两者之间存在很大的差异。 - rmaddy
@rmaddy 你说得对。在我看来,这种行为是未定义的,这可能是其中之一的结果。 - justintime

1

因为当前队列是主队列,当你继续在主队列上调用sync时,系统会认为当前主队列必须等待一些代码在当前队列中完成,但当前队列(即主队列)没有任何代码,所以你会一直等待:

苹果文档:调用此函数并针对当前队列会导致死锁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接