GCD 真的是线程安全的吗?

时间:2021-03-07 09:54:39

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

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

我认为Thread-Safe 和GCD 的Thread-Safe 的含义是不一样的,因为我测试了下面写的一些case 来求和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)
        }
    }
}

2 个答案:

答案 0 :(得分:3)

你说:

<块引用>

我研究了 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

答案 1 :(得分:1)

您当前的队列是并发的

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

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

<块引用>

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