簡體   English   中英

Swift - 設置變量屬性並發或多線程安全嗎?

[英]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
    }
}

更新1

感謝@MartinR 在評論中指出。

啟用“Thread Sanitizer”,它會立即報告錯誤。

更新2

如果將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 的代碼中,用引用類型變量,它會做三件事:

  1. 為類Data分配新內存
  2. 減少參照舊Data存儲在node.data ,甚至是免費的舊Data ,如果它不再引用
  3. node.data指向新Data

而對於值類型,它只會做一件事:

  1. 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM