[英]Swift - Is setting variable property concurrency or multithreading safe?
據我所知,在使用並發或多線程時設置變量屬性並不安全,但我無法使用以下代碼產生崩潰。
class Node {
var data = 0
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = i // Should get crash at this line
}
}
感謝@MartinR 在評論中指出。
啟用“Thread Sanitizer”,它會立即報告錯誤。
如果將data
更改為引用類型,代碼EXC_BAD_ACCESS KERN_INVALID_ADDRESS
發生EXC_BAD_ACCESS KERN_INVALID_ADDRESS
崩潰。 它並不總是發生,但有時會發生。 例如:
class Data {}
class Node {
var data = Data() // Use reference type instead of value type
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = Data() // EXC_BAD_ACCESS KERN_INVALID_ADDRESS
}
}
這種行為也發生在 Objective-C 中。 同時設置對象屬性會導致崩潰。 但是對於原始類型,崩潰不會發生。
如果有人也能解釋為什么同時設置引用類型屬性會導致崩潰,那就太完美了。
類值存儲在堆內存中,而結構/枚舉值存儲在堆棧內存中,編譯器將嘗試在編譯時分配這些內存(根據我的第一個參考和許多在線答案)。 您可以使用以下代碼進行檢查:
class MemTest {}
class Node {
var data = MemTest()
}
let node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for index in 0...100000 {
concurrentQueue.async {
node.data = MemTest()
withUnsafePointer(to: &node.data) {
print("Node data @ \($0)")
}
withUnsafePointer(to: node.data) {
print("Node data value no. \(index) @ \($0)")
}
}
如何:運行 2 次並檢查內存地址的值更改時間 500,在類和結構之間切換 MemTest 將顯示差異。 結構將顯示相同,而類將在每次之間顯示不同的地址。 所以改變值類型就像改變地址一樣,但不會恢復內存塊,而改變引用類型不僅是改變地址,還會恢復和分配新的內存塊,這會導致程序中斷。
但是如果用withUnsafePointer運行@trungduc的第一個代碼塊,它會告訴我們for循環的索引變量是在運行中和堆內存中分配的,那么為什么會這樣呢? 如前所述,如果值類型在編譯時可計算,編譯器只會嘗試分配內存。 如果它們不可計算,則該值將分配在堆內存中並一直保留到作用域結束(根據我的第二個參考)。 所以這里的解釋可能是系統將在一切完成后恢復分配的堆棧 - 范圍結束(我不太確定)。 所以在這種情況下,我們知道代碼不會產生崩潰。 我的結論是,在這種情況下,引用類型變量的 mem 將被無限制地分配和恢復,而值類型變量的 mem 只有在系統進入和退出包含所述變量的作用域后才會分配和刪除
我的回答不是很可靠,可能有很多語法和拼寫錯誤。 感謝所有更新。 提前致謝
對於我不太確定的部分:變量index
被復制到一個新的內存地址與操作符=
所以范圍在哪里結束無關緊要,堆棧將在 for 循環后釋放經過一些挖掘,在@trungduc 的代碼中,用引用類型變量,它會做三件事:
Data
分配新內存Data
存儲在node.data
,甚至是免費的舊Data
,如果它不再引用node.data
指向新Data
而對於值類型,它只會做一件事:
node.data
指向堆棧內存中的Integer
主要區別在於第 2 步,其中有可能恢復舊的Data
內存 引用類型可能會發生這種情況________Task 1________|________Task 2________
Allocate new Data #1 |
|Allocate new Data #2
Load pointer to old |
Data |
Reduce reference count|
to old Data |
|Load pointer to old
|Data
Free old Data |
|Reduce reference count
|to old Data (!)
|Free old Data (!)
Reference new Data #1 |
|Reference new Data #2
而對於值類型,這會發生
________Task 1________|________Task 2________
Reference to Integer 1|
|Reference to Integer 2
在第一種情況下,我們將有各種替代方案,但在大多數情況下,我們會遇到分段錯誤,因為線程 2 試圖在線程 1 釋放該指針后取消引用該指針。 正如我們所注意到的,可能還有其他問題,例如內存泄漏,線程 2 可能無法正確減少對數據 #1 的引用計數,而在第二種情況下,它只是更改了指針。
在第二種情況下,它永遠不會在 Intel CPU 上導致崩潰,但在其他 CPU 上也不能保證,因為許多 CPU 不承諾這樣做不會導致崩潰。
非原子並不意味着如果多個線程正在使用共享資源,應用程序將崩潰。
原子性質
將屬性定義為原子將保證將返回有效值。 請注意,有效並不總是意味着正確。
這也不意味着原子屬性是線程安全的。 不同的線程可以嘗試同時寫入和讀取。 將返回兩個值之一 - 更改前的值或更改的值
非原子屬性
非原子屬性不能保證返回值。 它可以是正確的值、部分寫入的值甚至是一些垃圾值。
它只是意味着最終值將不一致。 您將不知道哪個線程將最后更新該值。
您可以參考鏈接以獲取更多說明。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.