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, e.g. Does immutability guarantee thread safety? and Immutable objects are thread safe, but why?.
I also find a good article in Java on this topic.