假設我們有一個類屬性,它也是一個類:
class A { }
class B {
public var a: A // we want to init it in thread-safe manner
}
// Because 2 threads may be doing this
let b = B()
DispatchQueue.concurrentPerform(iterations: 3) { _ in
let x = b.a // expecting all threads to get the same instance of the property (i.e. same physical address in memory)
}
我知道lazy初始化不是執行緒安全的,但是其他選項呢?
例如:
1. 初始化屬性 inline 和 in 有區別init嗎?
即:
class B {
public var a: A = A()
}
比任何更好或更差
class B {
public var a: A
init() {
a = A()
}
從執行緒安全的角度來看?
2.init屬性本身是執行緒安全的嗎?
還是我們需要將它包裝在某種實體 getter(Java 風格)中,例如
extension A {
static var newInstance -> A {
return A()
}
}
class B {
public var a: A = A.newInstance
}
(Java 的基本原理是:在這種情況下,在創建實體之前不會回傳指標,而在直接 init 的情況下,會在 init 仍在運行時分配指標)。
注意:是的,我做了一些測驗。而且我沒有遇到“崩潰”/看不到上述任何一種方法的任何問題或差異 - 似乎上述所有三個選項都是平等的(從經驗測驗的角度來看)。但也許在某個地方有更明確的答案。
uj5u.com熱心網友回復:
初始化屬性 inline 與 in 之間有區別
init嗎?
init不,分配給 a 內部的屬性或為其提供默認值之間沒有有意義的區別init。默認屬性是在呼叫初始化程式之前立即分配的,所以
class X {
var y = Y()
var z: Z
init(z: Z) {
self.z = z
}
}
在概念上等同于
class X {
var y: Y
var z: Z
func _assignDefaultValues() {
y = Y()
}
init(z: Z) {
_assignDefaultValues()
self.z = z
}
}
這相當于
class X {
var y: Y
var z: Z
init(z: Z) {
y = Y()
self.z = z
}
}
換句話說,當到達 an 的末尾時init(...),所有存盤的屬性都必須完全初始化,并且使用默認值或顯式初始化它們沒有區別。
屬性本身是
init執行緒安全的嗎?
把這個分開,我相信這個問題有兩個組成部分:
- “到時候
init()回來,b.a保證分配到?”,和 - “如果是這樣,是否保證以其他讀取該值的執行緒將保證讀取與分配的值匹配的值,并且與其他執行緒看到的值匹配的方式完成分配?”,即讀取值而不撕裂?
(1) 的答案是肯定的。Swift 語言指南非常詳細地介紹了細節,但要特別說明:
在創建該類或結構的實體時,類和結構必須將其所有存盤的屬性設定為適當的初始值。存盤的屬性不能處于不確定狀態。
這意味著當您能夠b閱讀
let b = B()
b.a 必須已分配一個有效值A。
(2)的答案有點微妙。通常,Swift 不保證默認情況下的執行緒安全或原子行為,并且我找不到任何檔案,或者 Swift 源代碼中的參考表明 Swift 在初始化期間對 Swift 屬性的原子分配做出任何承諾. 盡管不可能證明是否定的,但我認為相對安全地說,Swift 不保證您在沒有顯式同步的情況下跨執行緒獲得一致的行為。
但是,可以保證的是,在 的生命周期內b,它在記憶體中具有穩定的地址,并且在那時,b.a也將具有穩定的地址。您的原始代碼片段在這種特定情況下似乎有效的至少部分原因是
- 所有執行緒都從記憶體中的相同地址讀取,
- 在 Swift 支持的許多(大多數?)平臺上,字長(在 32 位平臺上為 32 位;在 64 位平臺上為 64 位)讀取和寫入是原子的,并且不易被撕裂(只讀取一部分值在將另一部分寫入變數之前的變數)——并且 Swift 中的指標是字大小的。這并不能保證讀取和寫入將如您所愿跨執行緒同步,但您不會以這種方式獲得無效地址。但,
- 您的代碼在其他執行緒產生
b.a之前創建和分配,這意味著分配b.a更有可能在它們從該記憶體中讀取之前“通過”
b.a 如果您在生成之后開始分配 to ,concurrentPerform(iterations:)那么所有的賭注都將失敗,因為您將以意想不到的方式進行不同步的讀取和寫入交錯。
一般來說:
- 創建只讀資料并將其傳遞給多個執行緒并不安全,但在實踐中通常會按預期作業(但不應依賴!),
- 創建讀寫資料并將參考傳遞給多個執行緒是不安全的,并發突變也不會按預期作業,并且
- 如果您需要保證變數和同步的安全原子處理,建議您使用鎖或原子(例如來自官方swift-atomics)包之類的同步機制
如果還有疑問,建議您通過 LLVM 通過 Xcode 提供的清理工具運行代碼——特別是地址清理工具以捕獲任何與記憶體相關的問題,在這種情況下,執行緒清理工具也有助于捕獲同步問題和比賽條件。雖然并不完美,但這些工具可以幫助您確信您的代碼是正確的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/461346.html
