【问题标题】:is GCD really Thread-Safe?GCD 真的是线程安全的吗?
【发布时间】:2021-03-07 09:54:39
【问题描述】:

我研究过 GCD 和线程安全。 在苹果文档中,GCD 是线程安全的,这意味着多个线程可以访问。 而且我了解了线程安全的含义,即当多个线程访问某个对象时总是给出相同的结果。

我认为 Thread-Safe 和 GCD 的 Thread-Safe 的含义是不一样的,因为我测试了下面写的一些情况,总和为 0 到 9999。

当我多次执行以下代码时,“something.n”值不一样。 如果 GCD 是线程安全的,为什么 "something.n" 的值不一样?

我真的很困惑..你能帮帮我吗? 我真的很想掌握线程安全!!!

class Something {
    var n = 0
}
class ViewController: UIViewController {
    let something = Something()
    var concurrentQueue = DispatchQueue(label: "asdf", attributes: .concurrent)
    override func viewDidLoad() {
        super.viewDidLoad()
        let group = DispatchGroup()
        for idx in 0..<10000 {
            concurrentQueue.async(group: group) {
                self.something.n += idx
            }
        }
    
        group.notify(queue: .main ) {
            print(self.something.n)
        }
    }
}

【问题讨论】:

  • “在苹果文档中 ...” - 如果您应该分享相关文档的链接,并引用相关摘录,我们可能会更好地澄清他们在说什么。
  • 完全不相关,但要非常小心大型 for 循环,异步调度到某个队列。这是“线程爆炸”的教科书定义,因为 GCD 工作线程的数量非常有限,可能会导致性能欠佳,在某些情况下甚至会出现死锁。我确定上面的代码 sn-p 只是为了说明目的,但总的来说,要避免线程爆炸。例如。 gist.github.com/robertmryan/fb65510f42e315f24859cd5a24efae4f
  • developer.apple.com/library/archive/documentation/General/… 在页面底部,“调度队列本身是线程安全的。换句话说,您可以从系统上的任何线程向调度队列提交任务,而无需先获取锁或同步访问队列。”
  • 哦,你真好。我真的很感激你。谢谢!!是的,我只需要检查目的。哦,谢谢你给 concurrentPerfom()。
  • 啊,我现在理解混乱了。他们只是说,当您使用调度队列时,您可以从任何您想要的线程安全地使用它们(这是 100% 正确的)。但这并不意味着您使用 GCD 编写的代码本质上是线程安全的!如果你想编写线程安全的代码,你必须自己让它成为线程安全的。他们只是说队列本身可以安全地从您想要的任何线程中使用。

标签: swift thread-safety grand-central-dispatch


【解决方案1】:

你说:

我研究过 GCD 和线程安全。在苹果文档中,GCD 是线程安全的,这意味着多个线程可以访问。而且我了解了线程安全的含义,即当多个线程访问某个对象时总是给出相同的结果。

他们在说同样的话。一个代码块只有在可以安全地同时从不同线程调用它时才是线程安全的(并且这种线程安全性是通过确保代码的关键部分不能在一个线程上与另一个线程同时运行来实现的线程)。

但让我们明确一点:Apple 并不是说​​如果您使用 GCD,您的代码就自动是线程安全的。是的,调度队列对象本身是线程安全的(即,您可以安全地从任何您想要的线程调度到队列),但这并不意味着您自己的代码一定是线程安全的。如果一个人的代码同时从多个线程访问同一内存,则必须提供自己的同步以防止与任何其他访问同时写入。

在早于 GCD 的 Threading Programming Guide: Synchronization 中,Apple 概述了用于同步代码的各种机制。您还可以使用 GCD 串行队列进行同步。如果您使用并发队列,则如果您使用“屏障”进行写操作,则可以实现线程安全。实现线程安全的多种方式见this answer后半部分。

但要清楚,Apple 并没有引入不同的“线程安全”定义。正如他们在上述guide 中所说:

在线程安全方面,好的设计是您拥有的最佳保护。避免共享资源并最小化线程之间的交互可以降低这些线程相互干扰的可能性。然而,完全无干扰的设计并不总是可行的。在您的线程必须交互的情况下,您需要使用同步工具来确保它们在交互时安全地进行。

在引入 GCD 时发布的Concurrency Programming Guide: Migrating Away from Threads: Eliminating Lock-Based Code 中,Apple 表示:

对于线程代码,锁是同步对线程间共享资源的访问的传统方法之一。 ...您可以创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。

但他们并不是说你可以只使用 GCD 并发队列并自动实现线程安全,而是说通过谨慎和正确使用 GCD 队列,可以在不使用锁的情况下实现线程安全。


顺便说一句,Apple 提供了工具来帮助您诊断您的代码是否是线程安全的,即 Thread Sanitizer (TSAN)。见Diagnosing Memory, Thread, and Crash Issues Early。

【讨论】:

  • 天哪。我想知道如果 Rob 给我添加答案,我会很高兴。我不敢相信。在我问你一些事情之前,我应该告诉你,我真的很高兴并且很感激你。好的。我多次阅读你的评论。 “Apple 并不是说​​你可以使用 GCD 从多个线程安全访问。”正在打我。我几乎了解线程安全。
  • 串行队列是按顺序执行任务,直到任务完成。所以这是串行队列中的线程安全的。但是并发队列是按顺序执行任务,而不是等待任务完成。所以这不是线程安全的。对?但是为什么 Apple 引入 GCD 是因为它是 Thread-Safe 的?我认为“GCD 是线程安全的,但是如果您使用并发队列,那么在您提供同步之前,这不是线程安全的。 “我可以这样换吗?因为像我这样的人应该明白“GCD 是线程安全的! , 所以,GCD 的所有队列总是线程安全的! “
  • 可以从您想要的任何线程访问调度队列,因为队列本身是线程安全的。这与我们使用 GCD 的代码是否是线程安全的完全不同。为此,我们必须对我们的代码进行推理并弄清楚我们是否有任何“数据竞争”(两个不同的线程可能试图同时访问同一个非同步资源)。如果我们确实有数据竞赛,我们可以通过各种方式解决这个问题,GCD 或其他方式。但不要将队列的线程安全与我们代码的线程安全混为一谈。
  • 谢谢!!你是我的天使。我只是认为“我可以通过使用 Dispatch queue 获得线程安全的代码,因为 Dispatch queue ,thesevles 是线程安全的”但我的假设是错误的!谢谢。
【解决方案2】:

您当前的队列是并发的

var concurrentQueue = DispatchQueue(label: "asdf", attributes: . concurrent)

这意味着代码可以在每个async中以任意顺序运行

每次运行都有不同的顺序,但变量一次只能由一个部分访问

【讨论】:

  • 是的,我知道这是并发队列并按任何顺序执行。但为什么总和不一样?即使队列是并发队列,总和的结果不应该相同吗?因为线程安全给出相同的结果。
  • 当您将self.something.n 添加到自身时,由于执行任何其他部分而导致添加时的值可能会有所不同,您将线程安全与顺序混合,线程安全将保证从 1 访问变量随时部分,而顺序可能导致总和不同
  • 哦。是的!谢谢你。我差不多明白了。很多人说将被多个进程访问的对象应该是线程安全的,通过使用信号量或锁等objc.io/blog/2018/12/18/atomic-variables 所以我可以理解它的线程安全与 GCD 线程安全的含义有点不同?
  • @HyunSu 不,Apple 对线程安全没有不同的定义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-24
相关资源
最近更新 更多