块操作 - 完成块返回随机结果

Block Operation - Completion Block returning random results

我的块操作完成处理程序正在显示随机结果。不知道为什么。我读过这个,所有课程都说它类似于 GCD 中的 Dispatch Groups

请在下面找到我的代码

import Foundation

let sentence = "I love my car"
let wordOperation = BlockOperation()
var wordArray = [String]()

for word in sentence.split(separator: " ") {
  wordOperation.addExecutionBlock {
  print(word)
wordArray.append(String(word))
 }
}

wordOperation.completionBlock = {
    print(wordArray)
    print("Completion Block")
}

wordOperation.start()

我期望我的输出是 ["I"、"love"、"my"、"car"](它应该显示所有这些词 - 无论是按顺序还是按随机顺序)

但是当我 运行 我的输出是 ["my"] 或 ["love"] 或 ["I", "car"] - 它随机打印没有所有预期值

不确定为什么会这样。请指教

问题是那些单独的执行块可能 运行 在单独的线程上相互并发。如果您 start 像您一样的操作,或者即使您将此操作添加到 maxConcurrentOperationCount 为 1 的操作队列,也是如此。正如 the documentation 所说,在处理 addExecutionBlock:

The specified block should not make any assumptions about its execution environment.

除此之外,Swift 数组不是 thread-safe。因此,在没有同步的情况下,与非 thread-safe 对象的并发交互可能会导致意外行为,例如您与我们分享的内容。

如果您打开 TSAN,thread sanitizer,(在“产品”»“方案”»“编辑方案...”中找到,或按 +<,然后选择“运行”»“诊断”»“Thread Sanitizer”)它会警告您数据竞争。


所以,归根结底,问题不在于 addExecutionBlock 本身,而是试图同时从多个线程改变数组。如果您将并发队列与调度组结合使用,您可能会遇到类似的问题(尽管,像许多竞争条件一样,有时很难表现出来)。

理论上,可以将同步代码添加到您的代码片段中,这样就可以解决问题。但话又说回来,尝试启动一堆并发更新,然后才在其中使用同步来防止并发更新,这将是愚蠢的。它会起作用,但效率低下。仅当后台线程的工作量与同步更新某些共享资源所花费的时间量相比很大时,才采用该模式。但这里不是这样。