单例中吸气剂和定位器的螺纹安全性

Lee*_*fin 12 ios swift swift3

我在Swift 3中创建了一个简单的单例:

class MySingleton {
    private var myName: String
    private init() {}
    static let shared = MySingleton()

    func setName(_ name: String) {
        myName = name
    }

    func getName() -> String {
        return myName
    }
}
Run Code Online (Sandbox Code Playgroud)

由于我创建了init()私有的,也声明了shared实例static let,我认为初始化程序是线程安全的.但是getter和setter函数myName呢,它们是否安全?

Abi*_*ern 20

稍微不同的方法(这是来自Xcode 9 Playground)是使用并发队列而不是串行队列.

final class MySingleton {
    static let shared = MySingleton()

    private let nameQueue = DispatchQueue(label: "name.accessor", qos: .default, attributes: .concurrent)
    private var _name = "Initial name"

    private init() {}

    var name: String {
        get {
            var name = ""
            nameQueue.sync {
                name = _name
            }

            return name
        }
        set {
            nameQueue.async(flags: .barrier) {
                self._name = newValue
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 使用并发队列意味着来自多个线程的多个读取不会相互阻塞.由于获取时没有变异,因此可以同时读取该值,因为......
  • 我们使用.barrier异步调度设置新值.该块可以异步执行,因为调用者不需要等待设置该值.只有在其前面的并发队列中的所有其他块完成之后,才会运行该块.因此,当此setter等待运行时,现有的挂起读取不会受到影响.屏障意味着当它开始运行时,不会运行其他块.实际上,在设置器的持续时间内将队列转换为串行队列.在此块完成之前,不能再进一步读取.当块已完成时,已设置新值,此setter之后添加的任何getter现在可以同时运行.

  • 我非常喜欢这种变化.据我了解,就数据一致性而言,它将是"正确的".唯一的细微差别是对于设置者的调用语义将对于在"获取锁定"时可能期望延迟的新手有点违反直觉.基本上,在执行setter之后"有效设置"和"实际设置",但这实际上并不重要. (3认同)

all*_*enh 16

你写的那些获取者不是线程安全的,你是对的.在Swift中,目前实现此目的的最简单(读取最安全)方法是使用Grand Central Dispatch队列作为锁定机制.最简单(也是最容易理解)的方法是使用基本的串行队列.

class MySingleton {

    static let shared = MySingleton()

    // Serial dispatch queue
    private let lockQueue = DispatchQueue(label: "MySingleton.lockQueue")

    private var _name: String
    var name: String {
        get {
            return lockQueue.sync {
                return _name
            }
        }

        set {
            lockQueue.sync {
                _name = newValue
            }
        }
    }

    private init() {
        _name = "initial name"
    }
}
Run Code Online (Sandbox Code Playgroud)

使用串行调度队列将保证先进先出执行以及实现对数据的"锁定".也就是说,在更改数据时无法读取数据.在这种方法中,我们使用sync来执行实际的数据读取和写入,这意味着调用者总是被迫等待轮到其他类似于其他锁定原语.

注意:这不是最高效的方法,但它易于阅读和理解.它是避免竞争条件的良好通用解决方案,但并不意味着为并行算法开发提供同步.

来源:https : //mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html 什么是Swift等同于Objective-C的"@synchronized"?

  • 请注意,像“name +=“bar””这样的东西不是线程安全的,因为它由读取和写入操作组成,其中另一个线程可以在这两个操作之间修改名称,如这篇好文章中所述:https: //www.objc.io/blog/2018/12/18/atomic-variables/ (2认同)