Swift4变量是原子的吗?

时间:2018-03-06 03:57:23

标签: swift

我想知道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 取消注释

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 的输出: enter image description here 输出 NSLock
[1,2,3,...,2999,3000]

1 个答案:

答案 0 :(得分:8)

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

但在我们开始追逐之前,让我们看看原子财产是什么。

  

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

现在在你的代码中我们不讨论原子属性,因为+=操作实际上被分成了至少三个操作:

  1. 获取当前值,将其存储在CPU寄存器中
  2. 增加CPU寄存器
  3. 将增加的值存储到属性
  4. 即使setter是原子的,我们也可能会遇到两个线程同时出现的问题。达到#1并尝试以相同的值运行。

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

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

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

    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()
    

    您现在可以看到锁定的版本可以提供一致的结果,而非锁定的版本则不会。