【问题标题】:Are Swift4 variables atomic?Swift4 变量是原子的吗?
【发布时间】:2018-03-06 03:57:23
【问题描述】:

我想知道 Swift 4 变量是否是原子的。所以我做了以下测试。

以下是我的测试代码。

class Test {
    var count = 0
    let lock = NSLock()
   
    func testA() {
            count = 0
            
            let queueA = DispatchQueue(label: "Q1")
            let queueB = DispatchQueue(label: "Q2")
            let queueC = DispatchQueue(label: "Q3")
        
            queueA.async {
                for _ in 1...1000 {
                    self.increase()
                }
            }
            queueB.async {
                for _ in 1...1000 {
                    self.increase()
                }
            }
            queueC.async {
                for _ in 1...1000 {
                    self.increase()
                }
                
            }
    }

    ///The increase() method:
    func increase() {
//        lock.lock()
        self.count += 1
        print(count)
//        lock.unlock()
    }
}

输出如下,lock.lock()lock.unlock() 评论

3
3
3
4
5
...
2999
3000

输出如下,lock.lock()lock.unlock uncommented

1
2
3
4
5
...
2999
3000

我的问题
如果count变量是非原子变量,queueA、queueB和queueC应该异步调用increase(),导致随机访问和打印count

所以,在我看来,有一个时刻,例如,queueA 和 queueB 得到 count 等于 15,它们都将 count 增加 1(count += 1),所以计数应该是16 即使执行了两次增加。

但是上面的三个队列只是从第一个开始随机开始计数,然后一切都按预期进行。

最后,我的问题是为什么count 打印有序?

更新: 问题解决了,如果你想像我做的那样做实验,做如下修改。
1.把increase()改成下面这样,你会得到合理的输出。

func increase() {
    lock.lock()
    self.count += 1
    array.append(self.count)
    lock.unlock()
}

2.输出方式:

    @IBAction func tapped(_ sender: Any) {
        let testObjc = Test()
        testObj.testA()

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {
            print(self.testObj.array)
        }
    }

不带 NSLock 的输出: 使用 NSLock 输出:
[1,2,3,...,2999,3000]

【问题讨论】:

标签: swift


【解决方案1】:

不,默认情况下 Swift 属性不是原子的,是的,您很可能会遇到多线程问题,其中多个线程使用该属性的过时值,该属性刚刚更新。

但在我们开始之前,让我们看看原子属性是什么。

原子属性是具有原子设置器的属性 - 即,当设置器执行它的工作时,其他想要访问(获取或设置)该属性的线程被阻止。

现在在您的代码中,我们不是在谈论原子属性,因为 += 操作实际上至少分为三个操作:

  1. 获取当前值,将其存储在 CPU 寄存器中
  2. 增加 CPU 寄存器
  3. 将增加的值存储到属性中

即使 setter 是原子的,我们也可能会遇到两个线程“同时”到达 #1 并尝试对相同值进行操作的情况。

所以这里的问题应该是:increase() 是原子操作吗?


现在回到实际代码,是 print 调用“拯救”了你。增量和存储操作需要很短的时间,而打印需要更长的时间。这就是为什么您似乎没有遇到竞争条件的原因,因为多个线程可以使用过时值的窗口非常小。

尝试以下操作:同时取消注释 print 调用,并在足够长的时间后打印 count 值以使所有后台线程完成(2 秒应该足以进行 1000 次迭代):

let t = Test()
t.testA()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    // you're likely to get different results each run
    print(t.count)
}
RunLoop.current.run()

您现在会看到,锁定版本给出了一致的结果,而非锁定版本则没有。

【讨论】:

  • 感谢您的解释。在替换 print() the savior ` 之后,我得到了正确的输出。检查更新:D。
猜你喜欢
  • 2023-01-26
  • 2014-08-01
  • 2012-04-23
  • 2011-01-18
  • 2011-02-22
  • 2020-03-09
  • 1970-01-01
相关资源
最近更新 更多