DispatchQueue.global().sync 方法上的数据竞赛崩溃

问题描述 投票:0回答:1

我的代码非常简单。 我的理解是

remove
操作应该是线程安全的,因为我用
queue.sync

换行
let queue = DispatchQueue.global()

var fetchingInProgressList: Set<String> = []

swift multithreading thread-safety
1个回答
0
投票

tl;博士

不,您不能使用全局队列进行线程安全同步。不过,一个简单的串行队列就可以。或者,现在,在 Swift 并发中,我们可能会找到一个“演员”。

但是全局队列不足以实现线程安全。


如前所述,

global
返回一个并发队列。正如文档所说:

提交到返回队列的任务是相互并发调度的。

并且

sync
同步调用它:

该函数将一个块提交到指定的调度队列以同步执行...该函数直到该块完成后才返回。

因此,

sync
将阻塞调用者,直到分派的工作完成(即,它“同步”运行)。但是,
sync
对与同一并发队列交互的其他线程没有影响。分派到同一全局队列的工作可以与其他线程分派的工作并行运行。这违反了线程安全。这些不同的线程使用
sync
还是
async
都无关紧要。相关问题是队列是否可以阻止竞争,即阻止该队列上的并行执行。全局队列不提供这样的保证。

因此,简而言之,调度到全局队列(无论是同步还是异步)不足以实现线程安全。有两种传统技术可以通过 GCD 实现线程安全:

  1. 使用串行调度队列而不是并发队列。这样,读取和写入都不会彼此同时发生。

  2. 在调度写入时使用“屏障”。这称为“读者-作者”模式。但是您不能在全局队列上使用屏障,因此您只能在您自己的私有/自定义并发队列上执行此操作。 (FWIW,虽然读者-作者具有一定的直观吸引力,但我已将其从我的代码库中退休:需要代码清晰度和可维护性的地方有更好的模式。在那些罕见的计算密集型情况下,性能再次成为最重要的考虑因素,还有更好的选择。)

无论您选择上述哪一个,下一个问题是您是同步调度(

sync
)还是异步调度(
async
)。这取决于调用者是否必须等待分派的工作完成。例如,如果只是写入数据,我们可以使用
async
异步执行。但是,如果读取数据,我们显然必须使用
sync
同步执行此操作。

综上所述,如今,有了 Swift 并发性,我们会考虑使用 actor。 WWDC 视频中讨论了 Actor 使用 Swift Actor 保护可变状态使用 Swift Concurrency 消除数据争用

© www.soinside.com 2019 - 2024. All rights reserved.