简体   繁体   中英

How is struct (immutability) related to thread safety

I have read an article about immutability/struct in Swift . There's a part says:

In functional programming, side-effects are often considered bad, because they might influence your code in unexpected ways. For example, if an object is referenced in multiple places, every change automatically happens in every place. As we have seen in the introduction, when dealing with multi-threaded code, this can easily lead to bugs: because the object you are just checking can be modified from a different thread, all your assumptions might be invalid.

With Swift structs, mutating does not have the same problems. The mutation of the struct is a local side-effect, and only applies to the current struct variable. Because every struct variable is unique (or in other words: every struct value has exactly one owner), it's almost impossible to introduce bugs this way. Unless you're referring to a global struct variable across threads, that is.

In a bank account example, an account object has to be mutated or replaced at times. Using a struct is not sufficient for protecting an account from problems with updating it from multithreads. If we use some lock or queue, we can also use a class instance instead of a struct , right?

I tried writing some code that I'm not sure can result a race condition. The main idea is: two threads both first acquire some information (an instance of Account ) then overwrite the variable that hold the information, based on what was acquired.

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)")
        }
    }
}

If we don't modify the variable a (or, just use let to make it a constant), what's the benefit of using struct in place of class , in respect of thread-safety, for such a simple structure? There may be other benefits , though.

In real world, the a should be modified in a queue, or be protected by some lock, but where does the idea of "immutability helps with thread-safety" come from?

In the example above, is it still possible that both threads get the same value thus producing wrong result?

A quote from an answer on StackExchange :

Immutability gets you one thing: you can read the immutable object freely, without worrying about its state changing underneath you

So is the benefit is for read-only?

There's similar questions in Java, eg Does immutability guarantee thread safety? and Immutable objects are thread safe, but why? .

I also find a good article in Java on this topic.

Other resources:

I'm not 100% sure I understand what you are trying to ask but I'll take a swing anyway since I've been studying Swift structures a lot recently and feel like I have a solid answer for you. There seems to be some confusion here about value types in Swift.

First, your code will not result in race conditions because as you note in your comment, structures are value types and therefore assign-by-copy (aka when assigned to a new variable, a deep copy of the value type is made). Each thread (queue) here will have its own thread-local copy of the structure assigned to temp which by default prevents the race conditions from occurring (which contributes to the thread safety of structures). They are never accessed (or accessible) from outside threads.

Also

We can't really mutated a struct in Swift, even when we call a mutating method, because a new one is created, right?

isn't necessarily correct. From the Swift Docs:

However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behavior for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends . The method can also assign a completely new instance to its implicit self property, and this new instance will replace the existing one when the method ends.

I take this to mean that changes are sometimes actually made to the original structure's values and in a few cases a completely new instance can be assigned to self. Neither of these provide direct "benefits" to multi-threading and are just how the value types in Swift work. The "benefits" actually come from the "opting in" in to mutable behavior.

where does the idea of "immutability helps with thread-safety" come from?

Again value types stress the importance of value over identity so we should be able to make some reliable assumptions about their values. If a value type were mutable by default, it becomes much more difficult to predictions/assumptions about the value (and what it will be). If something is immutable it becomes much easier to reason and make assumptions about. We can assume that an immutable value is the same in any thread we pass it to.

If we have to "turn on" mutability it makes it easier to pinpoint where changes to the value type are coming from and helps cut down on errors generated from accidental changes to a value. Additionally its still relatively simple to reason and make predictions about the value since we know exactly what methods are able to make changes and we (hopefully) know under what conditions/circumstances the mutating methods will run

This is the opposite of other languages that I can think of, like C++, where you imply that a method cannot change the value of a member by adding the keyword const to the end of the method header.

If we don't modify the variable a (or, just use let to make it a constant ),

If we were to use a class, even in the simple situation such as the one you describe, then using even a let assignment is not enough to prevent side effects from occurring. It is still vulnerable because its a reference type. Even within the same thread, a let assignment to a class type does not stop side effects.

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

This code results in the following output:

 Hello, playgroundfoo
 Hello, playgroundfoo

Compare the above code to a similar operation with Strings (a Swift Standard structure type) and you will see the difference in results.

TL;DR Its much easier to make predictions about values that never change or can only change under specific circumstances.

If anyone who's got a better understanding would like to correct my answer or point out what I got wrong please feel free.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM