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.
Does immutability entirely eliminate the need for locks in multi-processor programming?
An immutable object is one whose state can't be changed once the object is created. Immutable objects are, by their very nature, thread-safe simply because threads have to be able to write to an object's instance variables to experience a read/write or write/write conflict.
Structs are thread-safe Since structs are unique copies, it's only possible for one thread at a time to manage them under most circumstances. This prevents them from being accessed by more than one thread simultaneously which can prevent difficult-to-track bugs.
In computer programming, thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads.
Immutable objects are useful in multithreaded applications because they can be safely accessed by several threads concurrently, without the need for locking or other synchronization.
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), what's the benefit of using struct in place of class, in respect of thread-safety, for such a simple structure?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With