[英]How is struct (immutability) related to thread safety
我讀過一篇關於Swift中不變性/結構的文章 。 有一部分說:
在函數式編程中,副作用通常被認為是不好的,因為它們可能以意想不到的方式影響您的代碼。 例如,如果在多個位置引用了一個對象,則每個更改都會自動在每個位置發生。 正如我們在簡介中所看到的,在處理多線程代碼時,這很容易導致錯誤:因為您可以從另一個線程修改您剛剛檢查的對象,所以所有假設都可能無效。
使用Swift結構,變異不會有相同的問題。 該結構的突變是局部副作用,僅適用於當前的結構變量。 因為每個struct變量都是唯一的(或者換句話說:每個struct值都只有一個所有者),所以幾乎不可能以這種方式引入錯誤。 除非您是跨線程引用全局結構變量,否則就是這樣。
在銀行帳戶示例中,有時必須更改或替換帳戶對象。 使用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
代替class
的好處是什么? 不過, 可能還有其他好處 。
在現實世界中, a
應在隊列中進行修改,或者通過一些鎖保護,但哪里的想法“不變性與線程安全幫助”從何而來?
在上面的示例中,兩個線程是否仍有可能獲得相同的值,從而產生錯誤的結果?
不變性讓您一件事:您可以自由讀取不可變對象,而不必擔心其下方的狀態發生變化
那么好處是只讀的嗎?
Java中也存在類似的問題,例如, 不變性可以確保線程安全嗎? 和不可變對象是線程安全的,但是為什么呢? 。
我不是100%肯定我了解您要問的問題,但是由於我最近一直在研究Swift結構,因此我還是會搖擺不定,覺得我為您提供了一個可靠的答案。 關於Swift中的值類型,這里似乎有些混亂。
首先,您的代碼不會導致爭用條件,因為正如您在注釋中指出的那樣,結構是值類型,因此是按拷貝分配的(也就是,當分配給新變量時,將創建值類型的深層副本)。 這里的每個線程(隊列)將具有分配給temp
的結構自己的線程本地副本,默認情況下,該副本可防止出現競爭狀況(這有助於結構的線程安全)。 永遠不會從外部線程訪問(或訪問)它們。
也
即使我們調用了mutating方法,我們也無法真正在Swift中對結構進行突變,因為創建了一個新方法,對嗎?
不一定正確。 從Swift文檔:
但是,如果需要在特定方法中修改結構或枚舉的屬性,則可以選擇對該方法進行行為更改。 然后,該方法可以從方法內部改變(即更改)其屬性,並在方法結束時將其所做的任何更改寫回到原始結構 。 該方法還可以為其隱式的self屬性分配一個全新的實例,並且該新實例將在方法結束時替換現有實例。
我的意思是有時實際上會對原始結構的值進行更改,並且在某些情況下可以為self分配一個全新的實例。 這些都不能為多線程提供直接的“好處”,而僅僅是Swift中值類型的工作方式。 實際上,“好處”來自“選擇加入”到易變的行為。
“不變性有助於線程安全”的思想從何而來?
同樣,價值類型強調價值高於身份的重要性,因此我們應該能夠對其價值做出一些可靠的假設。 如果值類型默認情況下是可變的,則對值(及其值)的預測/假設將變得更加困難。 如果某些東西是不可變的,則推理和做出假設變得容易得多。 我們可以假定一個不變值在傳遞給它的任何線程中都是相同的。
如果我們必須“啟用”可變性,則可以更輕松地查明值類型的更改來自何處,並有助於減少因意外更改值而產生的錯誤。 此外,由於我們確切知道哪些方法可以進行更改,並且(希望)知道變異方法將在什么條件/情況下運行,因此它仍然相對容易地推理和對值進行預測。
這與我能想到的其他語言相反,例如C ++,在這種語言中,您暗示方法無法通過在方法標題的末尾添加關鍵字const
來更改成員的值。
如果我們不修改變量
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可以很容易地預測不會改變或只能在特定情況下改變的值。
如果有任何更好的了解的人想糾正我的答案或指出我的錯誤,請放心。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.