代码之家  ›  专栏  ›  技术社区  ›  JsW

Swift4变量是原子变量吗?

  •  10
  • JsW  · 技术社区  · 8 年前

    我想知道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.unlock 未注释

    1
    2
    3
    4
    5
    ...
    2999
    3000
    

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

    所以,在我看来,有一个时刻,例如,queueA和queueB得到 计数 等于15,两者都增加 计数 由1( count += 1 ),因此计数应为16,即使执行了两次递增。

    但上面的三个队列只是从一开始就随机开始计数,然后一切正常。

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

    使现代化 : 问题解决了,如果你想像我一样做实验,请做以下更改。
    1、更改 增加() 对于以下内容,您将获得合理的输出。

    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 回复  |  直到 5 年前
        1
  •  11
  •   Cristik    8 年前

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

    但在我们开始讨论之前,让我们看看原子的性质是什么。

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

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

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

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

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


    现在回到实际代码,它是 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()
    

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