Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 单例中getter和setter的线程安全性_Ios_Swift_Swift3 - Fatal编程技术网

Ios 单例中getter和setter的线程安全性

Ios 单例中getter和setter的线程安全性,ios,swift,swift3,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 myN

我在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
    }
}

由于我将
init()
设置为私有,并将
shared
实例声明为
static let
,因此我认为初始值设定项是线程安全的。但是对于
myName
的getter和setter函数,它们是线程安全的吗?

您所编写的getter不是线程安全的,这是正确的。在Swift中,目前实现这一点最简单(最安全)的方法是使用大型中央调度队列作为锁定机制。实现这一点的最简单(也是最容易推理的)方法是使用基本串行队列

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"
    }
}
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
            }
        }
    }
}
使用串行调度队列将保证先进先出的执行,并实现对数据的“锁定”。也就是说,数据在更改时无法读取。在这种方法中,我们使用
sync
来执行数据的实际读取和写入,这意味着调用方将总是被迫等待轮到它,类似于其他锁定原语

注意:这不是方法,但它很容易阅读和理解。这是一个很好的通用解决方案,可以避免竞争条件,但并不意味着为并行算法开发提供同步

资料来源:

一种稍有不同的方法(来自Xcode 9游乐场)是使用并发队列而不是串行队列

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"
    }
}
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
            }
        }
    }
}
  • 使用并发队列意味着来自多个线程的多个读取不会相互阻塞。由于获取时没有变异,因此可以同时读取该值,因为
  • 我们正在使用
    .barrier
    异步调度设置新值。该块可以异步执行,因为调用者不需要等待设置值。在前面的并发队列中的所有其他块完成之前,该块将不会运行。因此,当此setter等待运行时,现有的挂起读取不会受到影响。屏障意味着当它开始运行时,不会运行其他块。有效地,在setter期间将队列转换为串行队列。在该块完成之前,无法进行进一步读取。当块完成并设置了新值时,在此setter之后添加的任何getter现在都可以并发运行

我认为它不是线程安全的,即使它是单线程的还是无线程的?如何使它成为线程安全的?添加一个串行调度队列作为类的成员,并将get/set操作列为“调度障碍”来处理读写器问题:这可能值得考虑-这有关系吗?使用屏障等使该线程安全。假设两个不同的线程相互依赖执行顺序。在某些情况下,his有明确的意义(如生产者/消费者模式),但线程之间相互依赖的程度越低越好。值得一提的是,没有什么比“在swift中实现这一点的最佳方法”更好的了。线程安全或并发编程和所使用的编程语言并没有任何关系,也和单例结构并没有任何关系。首先要回答的问题不是如何同步数据,而是为什么需要同步数据。如果没有什么优势,最好避免任何类型的并行或并发。你的答案几乎是正确的,这个问题非常模糊…@user3441734因为GCD(或其他技术)并非在所有环境中都可用,我认为这是有联系的。这个问题以单身为前提,但最终这个问题与单身无关。说“最好避免任何类型的并行或并发”是非常糟糕的建议。事实上,在iOS中避免并发比简单地接受和理解它更具挑战性。我写道“如果没有什么优势,最好避免…”我对GCD非常感兴趣,我几乎在我所有的项目中都使用它。只是要小心,
name+=“bar”之类的东西
不是线程安全的,因为它包含一个读操作和一个写操作,另一个线程可以在这两个操作之间修改名称,就像这篇漂亮的文章中所描述的:非常有用的文章,也是最近的,非常感谢@d4Rk!我真的很喜欢这种变化。据我所知,它在数据一致性方面同样“正确”。唯一的细微差别是对setter的调用语义对于新手来说有点违反直觉,因为新手可能在“获取锁”时期待延迟。基本上,在执行setter之后,“有效设置”和“事实设置”,但这并不重要。@AllenHumphreys,使用“您的”(序列)或“Abizern的”(屏障)的方法取决于很多因素。在并发队列上使用屏障并不是违反直觉的,而是不同的。它有一些优点,也有一些缺点。有时它们都会失败。这与在串行队列上运行
set
async和
get
sync类似吗?@CyberMew这不是一回事。使用并发队列的意义在于,如果有多个读取,则不会相互阻塞。我概述的小偷方法意味着队列在读取时是并发的,但在写入时它表现为串行队列。是的。如果您开始写入,则随后添加的所有读卡器都将被阻止,直到写入完成