struct(immutability)如何与线程安全相关

时间:2017-09-15 04:26:54

标签: swift multithreading struct thread-safety immutability

我看过an article about immutability/struct in Swift。有一部分说:

  

在函数式编程中,副作用通常被认为是不好的,   因为它们可能以意想不到的方式影响您的代码对于   例如,如果在多个位置引用对象,则每次更改   自动发生在每个地方。正如我们在中看到的那样   介绍,在处理多线程代码时,这很容易   导致错误:因为您正在检查的对象可以修改   从不同的主题,您的所有假设都可能无效。

     

使用Swift结构,变异没有同样的问题。该   结构的突变是局部的副作用,只适用于   当前结构变量。因为每个结构变量都是唯一的(或者   换句话说:每个结构值只有一个所有者),它几乎就是   不可能以这种方式引入错误。 除非您指的是   跨线程的全局结构变量,即。

在银行帐户示例中,帐户对象有时必须变异或更换。使用struct不足以保护帐户免受从多线程更新的问题。 如果我们使用某个锁或队列,我们​​也可以使用class实例而不是struct,对吗?

我尝试编写一些我不确定会导致竞争条件的代码。主要思想是:两个线程首先获取一些信息(Account的实例),然后根据获取的内容覆盖保存信息的变量。

struct Account {
    var balance = 0

    mutating func increase() {
        balance += 1
    }
}

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // method1()
        method2()
    }

    func method1() {
        var a = Account(balance: 3) {
            didSet {
                print("Account changed")
            }
        }

        // https://stackoverflow.com/questions/45912528/is-dispatchqueue-globalqos-userinteractive-async-same-as-dispatchqueue-main
        let queue = DispatchQueue.global()

        queue.async {
            a.increase()
            print("global: \(a.balance)")
        }

        DispatchQueue.main.async {
            a.increase()
            print("main: \(a.balance)")
        }
    }

    func method2() {
        var a = Account(balance: 3) {
            didSet {
                print("Account changed")
            }
        }

        func updateAccount(account : Account) {
            a = account
        }

        let queue = DispatchQueue.global()

        queue.async { [a] in
            var temp = a
            temp.increase()
            updateAccount(account: temp) // write back
            print("global: \(temp.balance)")
        }

        DispatchQueue.main.async { [a] in
            var temp = a
            temp.increase()
            updateAccount(account: temp)
            print("main: \(temp.balance)")
        }
    }
}

如果我们不修改变量a(或者只使用let使其成为常量),使用struct取代{的好处是什么? {1}},就线程安全而言,对于这样一个简单的结构? There may be other benefits

在现实世界中,class应该在队列中修改,或者受到某些锁定的保护,但是“不变性对线程安全有帮助”的想法来自哪里?

在上面的例子中,两个线程是否仍然可以获得相同的值,从而产生错误的结果?

来自an answer on StackExchange的引用:

  

不变性让你有一件事:你可以读取不可变对象   自由地,不用担心它在你下面的状态变化

这样做的好处是只读吗?

Java中存在类似的问题,例如: Does immutability guarantee thread safety?Immutable objects are thread safe, but why?

我还在Java中找到了关于这个主题的a good article

其他资源:

1 个答案:

答案 0 :(得分:2)

我不确定是否100%理解您要提出的问题,但是由于最近我一直在研究Swift结构,因此我还是会摇摆不定,觉得我为您提供了一个可靠的答案。这里似乎对Swift中的值类型有些困惑。

首先,您的代码不会导致竞争条件,因为正如您在注释中所指出的那样,结构是值类型,因此按拷贝分配(也就是当分配给新变量时,将创建值类型的深层副本) )。这里的每个线程(队列)都将拥有分配给temp的结构自己的线程本地副本,默认情况下,该副本可防止出现竞争情况(这有助于结构的线程安全)。永远不会从外部线程访问(或访问)它们。

  

即使在调用mutating方法时,我们也无法真正在Swift中对结构进行突变,因为创建了一个新方法,对吧?

不一定正确。来自Swift文档:

  

但是,如果您需要在特定方法中修改结构或枚举的属性,则可以选择对该方法的行为进行更改。然后,该方法可以从方法内部更改(即更改)其属性,并且在方法结束时将其所做的任何更改写回到原始结构。该方法还可以为其隐式的self属性分配一个全新的实例,该新实例将在方法结束时替换现有实例。

我的意思是有时实际上会对原始结构的值进行更改,并且在某些情况下可以为self分配一个全新的实例。这些都不能为多线程提供直接的“好处”,而仅仅是Swift中值类型的工作方式。实际上,“好处”来自“选择加入”到易变的行为。

  

“不变性有助于线程安全”的思想从何而来?

再次的价值类型强调了价值高于身份的重要性,因此我们应该能够对其价值做出一些可靠的假设。如果值类型默认情况下是可变的,则对值(及其值)的预测/假设将变得更加困难。如果某些东西是不可变的,则推理和做出假设变得容易得多。我们可以假定一个不变值在传递给它的任何线程中都是相同的。

如果我们必须“打开”可变性,则可以更轻松地查明值类型的更改来自何处,并有助于减少因意外更改值而产生的错误。此外,它仍然相对容易推理和对值进行预测,因为我们确切知道哪些方法可以进行更改,并且(希望)知道变异方法将在何种条件/情况下运行

这与我能想到的其他语言相反,例如C ++,您在其中暗示一种方法无法通过在方法标题的末尾添加关键字const来更改成员的值。 / p>

  

如果我们不修改变量a(或者只需使用let使其成为常量),使用struct代替类的好处是什么,就线程安全而言,这么简单的结构?

如果我们要使用一个类,即使在诸如您所描述的那种简单情况下,那么即使使用let分配也不足以防止产生副作用。它仍然是脆弱的,因为它是引用类型。即使在同一线程内,对类类型的let分配也不会停止副作用。

class StringSim{
var heldString:String

init(held: String){
    heldString = held
}

func display(){
    print(heldString)
}

func append(str: String){
    heldString.append(str)
}
}
let myStr = StringSim(held:"Hello, playground")
var mySecStr = myStr
mySecStr.append(str: "foo")
myStr.display()
mySecStr.display()

此代码将产生以下输出:

 Hello, playgroundfoo
 Hello, playgroundfoo

将上述代码与使用Strings(Swift Standard结构类型)的类似操作进行比较,您将看到结果的差异。

TL; DR 对永不更改或仅在特定情况下只能更改的值进行预测要容易得多。

如果有一个更好的了解的人想纠正我的答案或指出我的错误之处,请放心。