https://www.mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.htmlhtml
https://swift.gg/2018/06/07/friday-qa-2015-02-06-locks-thread-safety-and-swift/python
在 Swift 中有個有趣的現象:它沒有與線程相關的語法,也沒有明確的互斥鎖/鎖(mutexes/locks
)概念,甚至 Objective-C 中有的 @synchronized
和原子屬性它都沒有。幸運的是,蘋果系統的 API 能夠很是容易地應用到 Swift 中。今天,我會介紹這些 API 的用法以及從 Objective-C 過渡的一些問題,這些靈感都來源於 Cameron Pulsford。swift
鎖(lock
)或者互斥鎖(mutex
)是一種結構,用來保證一段代碼在同一時刻只有一個線程執行。它們一般被用來保證多線程訪問同一可變數據結構時的數據一致性。主要有下面幾種鎖:安全
Blocking locks
):常見的表現形式是當前線程會進入休眠,直到被其餘線程釋放。Spinlocks
):使用一個循環不斷地檢查鎖是否被釋放。若是等待狀況不多話這種鎖是很是高效的,相反,等待狀況很是多的狀況下會浪費 CPU 時間。Reader/writer locks
):容許多個讀線程同時進入一段代碼,但當寫線程獲取鎖時,其餘線程(包括讀取器)只能等待。這是很是有用的,由於大多數數據結構讀取時是線程安全的,但當其餘線程邊讀邊寫時就不安全了。Recursive locks
):容許單個線程屢次獲取相同的鎖。非遞歸鎖被同一線程重複獲取時可能會致使死鎖、崩潰或其餘錯誤行爲。蘋果提供了一系列不一樣的鎖 API,下面列出了其中一些:數據結構
pthread_mutex_t
pthread_rwlock_t
dispatch_queue_t
NSOperationQueue
當配置爲 serial
時NSLock
OSSpinLock
除此以外,Objective-C 提供了 @synchronized
語法結構,它其實就是封裝了 pthread_mutex_t
。與其餘 API 不一樣的是,@synchronized
並未使用專門的鎖對象,它能夠將任意 Objective-C 對象視爲鎖。@synchronized(someObject)
區域會阻止其餘 @synchronized(someObject)
區域訪問同一對象指針。不一樣的 API 有不一樣的行爲和能力:多線程
pthread_mutex_t
是一個可選擇性地配置爲遞歸鎖的阻塞鎖;pthread_rwlock_t
是一個阻塞讀寫鎖;dispatch_queue_t
能夠用做阻塞鎖,也能夠經過使用 barrier block 配置一個同步隊列做爲讀寫鎖,還支持異步執行加鎖代碼;NSOperationQueue
能夠用做阻塞鎖。與 dispatch_queue_t
同樣,支持異步執行加鎖代碼。NSLock
是 Objective-C 類的阻塞鎖,它的同伴類 NSRecursiveLock
是遞歸鎖。OSSpinLock
顧名思義,是一個自旋鎖。最後,@synchronized
是一個阻塞遞歸鎖。閉包
注意,pthread_mutex_t
,pthread_rwlock_t
和 OSSpinLock
是值類型,而不是引用類型。這意味着若是你用 =
進行賦值操做,實際上會複製一個副本。這會形成嚴重的後果,由於這些類型沒法複製!若是你不當心複製了它們中的任意一個,這個副本沒法使用,若是使用可能會直接崩潰。這些類型的 pthread
函數會假定它們的內存地址與初始化時同樣,所以若是將它們移動到其餘地方就可能會出問題。OSSpinLock
不會崩潰,但複製操做會生成一個徹底獨立的鎖,這不是你想要的。併發
若是使用這些類型,就必須注意不要去複製它們,不管是顯式的使用 =
操做符仍是隱式地操做。
例如,將它們嵌入到結構中或在閉包中捕獲它們。框架
另外,因爲鎖本質上是可變對象,須要用 var
來聲明它們。異步
其餘鎖都是是引用類型,它們能夠隨意傳遞,而且能夠用 let
聲明。
2015-02-10 更新:本節中所描述的問題已經以驚人的速度被淘汰。蘋果昨天發佈了 Xcode 6.3 beta 1,其中包括 Swift 1.2。在其餘更改中,如今使用一個空的初始化器導入 C 結構,將全部字段設置爲零。簡而言之,你如今能夠直接使用 pthread_mutex_t()
,不須要下面提到的擴展。
pthread 類型很難在 swift 中使用。它們被定義爲不透明的結構體中包含了一堆存儲變量,例如:
struct _opaque_pthread_mutex_t { |
目的是聲明它們,而後使用 init 函數對它們進行初始化,使用一個指針存儲和填充。在 C 中,它看起來像:
pthread_mutex_t mutex; |
這段代碼能夠正常的工做,只要你記得調用 pthread_mutex_init
。然而,Swift 真的真的不喜歡未初始化的變量。與上面代碼等效的 Swift 版本沒法編譯:
var mutex: pthread_mutex_t |
Swift 須要變量在使用前初始化。pthread_mutex_init
不使用傳入的變量的值,只是重寫它,可是 Swift 不知道,所以它產生了一個錯誤。爲了知足編譯器,變量須要用某種東西初始化。在類型以後使用 ()
,但這樣寫仍然會報錯:
var mutex = pthread_mutex_t() |
Swift 須要那些不透明字段的值。__sig
能夠傳入零,可是 __opaque
就有點煩人了。下面的結構體須要橋接到 swift 中:
struct _opaque_pthread_mutex_t { |
目前沒有簡單的方法使用一堆 0 構建一個元組,只能像下面這樣把全部的 0 都寫出來:
var mutex = pthread_mutex_t(__sig: 0, |
這麼寫太難看了,但我沒找到好的方法。我能想到最好的作法就是把它寫到一個擴展中,這樣直接使用空的 ()
就能夠了。下面是我寫的兩個擴展:
extension pthread_mutex_t { |