天天看點

Swift 線程安全數組

有并發的地方就存線上程安全問題,尤其是對于 Swift 這種還沒有内置并發支援的語言來說線程安全問題更為突出。下面我們通過常見的數組操作來分析其中存在的線程問題,以及如何實作一個線程安全數組。

因為無法确定執行順序,是以并發導緻的問題一般都很難模拟和測試。不過我們可以通過下面這段代碼來模拟一個并發情形下導緻的資料競争問題。

這段代碼中我們對數組 array 進行了 1000 次并發修改操作,雖然有些誇張但是它能很好的揭示一些并發環境下數組寫操作存在的一些問題。因為對于值類型來說 Swift 采用的是 Copy On Write 機制,是以在進行 Copy On Write 處理是可能數組已經被另一個寫操作給修改了。這就造成了數組中元素和資料的丢失現象,如下:

這應該是大家都能想到的一種最常見處理方式。 由于串行隊列每次都隻能運作一個程序,是以即使有多個數組寫操作程序我們也能確定資源的互斥通路。這樣數組是從設計的并發程序安全的。

由于寫操作并不需要傳回操作結果,所有這裡可以使用異步的方式進行。而對于讀操作來說則必須采用同步的方式實時傳回操作結果。但是串行隊列有一個最為明顯的缺陷:多個讀操作之間也是互斥的。很顯然這種方式太過粗暴存在明顯的性能問題,畢竟讀操作的頻率直覺上是要高過寫操作的。

采用并發隊列我們就可以很好的解決上面提到的多個讀操作的性能問題,不過随之而來的就是寫操作的資料競争。這與我們在學習作業系統是的 讀者-作者 問題本質上是一類問題,我們可以通過共享互斥鎖來解決寫操作的資料競争問題。對于 iOS 來說它就是 GCD 中的寫欄栅 barrier 機制。

上面代碼中我們對異步的寫操作設定了 barrier 标示,這意味着在執行異步操作代碼的時候隊列不能執行其他代碼。而對于同步的讀操作來說,由于是并發隊列同時讀取資料并不會存在任何性能問題。

通過 filePrivate 屬性 array 和 queue , SafeArray 成功的實作了大多數數組常用功能,更為關鍵的是該類型并發安全:所有的寫操作都通過 barrier 方式的異步進行,而讀操作則與内置 Array 沒有什麼差別。

需要注意的是:我們使用同樣的方式可以實作并發安全的 Dictionary 類似:SynchronizedDictionary。

接下來,我們可以對傳統的非并發安全數組和 SafeArray 進行以下比較:

得到的輸出可能如下:

雖然由于使用了 GCD 機制導緻速度慢了 30% 左右并且使用了更多的記憶體,但是與之對應的是我們實作了一個并發安全的數組類型。

原文位址