iOS 中的各種鎖

在日常開發過程中,為了提升程序運行效率,以及用戶體驗,我們經常使用多線程。在使用多線程的過程中,難免會遇到資源競爭問題。我們采用鎖的機制來確保線程安全。

線程安全

當一個線程訪問數據的時候,其他的線程不能對其進行訪問,直到該線程訪問完畢。即,同一時刻,對同一個數據操作的線程只有一個。只有確保了這樣,才能使數據不會被其他線程污染。而線程不安全,則是在同一時刻可以有多個線程對該數據進行訪問,從而得不到預期的結果。

比如寫文件和讀文件,當一個線程在寫文件的時候,理論上來說,如果這個時候另一個線程來直接讀取的話,那么得到將是不可預期的結果。

為了線程安全,我們可以使用鎖的機制來確保,同一時刻只有同一個線程來對同一個數據源進行訪問。在開發過程中我們通常使用以下幾種鎖。

NSLock

NSRecursiveLock

NSCondition

NSConditionLock

pthread_mutex

pthread_rwlock

POSIX Conditions

OSSpinLock

os_unfair_lock

dispatch_semaphore

@synchronized

信號量

在多線程環境下用來確保代碼不會被并發調用。在進入一段代碼前,必須獲得一個信號量,在結束代碼前,必須釋放該信號量,其他想要想要執行該代碼的線程必須等待直到前者釋放了該信號量。

以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知后,打開車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。

在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。

互斥鎖

一種用來防止多個線程同一時刻對共享資源進行訪問的信號量,它的原子性確保了如果一個線程鎖定了一個互斥量,將沒有其他線程在同一時間可以鎖定這個互斥量。它的唯一性確保了只有它解鎖了這個互斥量,其他線程才可以對其進行鎖定。當一個線程鎖定一個資源的時候,其他對該資源進行訪問的線程將會被掛起,直到該線程解鎖了互斥量,其他線程才會被喚醒,進一步才能鎖定該資源進行操作。

優點:能有效防止因多線程搶奪資源造成的數據安全問題;

缺點:需要消耗大量的CPU資源;

NSLock

NSLock實現了最基本的互斥鎖,遵循了 NSLocking 協議,通過 lock 和 unlock 來進行鎖定和解鎖。其使用也非常簡單

由于是互斥鎖,當一個線程進行訪問的時候,該線程獲得鎖,其他線程進行訪問的時候,將被操作系統掛起,直到該線程釋放鎖,其他線程才能對其進行訪問,從而卻確保了線程安全。但是如果連續鎖定兩次,則會造成死鎖問題。那如果想在遞歸中使用鎖,那要怎么辦呢,這就用到了 NSRecursiveLock 遞歸鎖。

NSRecursiveLock

遞歸鎖,顧名思義,可以被一個線程多次獲得,而不會引起死鎖。它記錄了成功獲得鎖的次數,每一次成功的獲得鎖,必須有一個配套的釋放鎖和其對應,這樣才不會引起死鎖。只有當所有的鎖被釋放之后,其他線程才可以獲得鎖

NSCondition

NSCondition 是一種特殊類型的鎖,通過它可以實現不同線程的調度。一個線程被某一個條件所阻塞,直到另一個線程滿足該條件從而發送信號給該線程使得該線程可以正確的執行。比如說,你可以開啟一個線程下載圖片,一個線程處理圖片。這樣的話,需要處理圖片的線程由于沒有圖片會阻塞,當下載線程下載完成之后,則滿足了需要處理圖片的線程的需求,這樣可以給定一個信號,讓處理圖片的線程恢復運行。

NSConditionLock

NSConditionLock 對象所定義的互斥鎖可以在使得在某個條件下進行鎖定和解鎖。它和 NSCondition 很像,但實現方式是不同的。

當兩個線程需要特定順序執行的時候,例如生產者消費者模型,則可以使用 NSConditionLock 。當生產者執行執行的時候,消費者可以通過特定的條件獲得鎖,當生產者完成執行的時候,它將解鎖該鎖,然后把鎖的條件設置成喚醒消費者線程的條件。鎖定和解鎖的調用可以隨意組合,lock 和 unlockWithCondition: 配合使用 lockWhenCondition: 和 unlock 配合使用。

當生產者釋放鎖的時候,把條件設置成了1。這樣消費者可以獲得該鎖,進而執行程序,如果消費者獲得鎖的條件和生產者釋放鎖時給定的條件不一致,則消費者永遠無法獲得鎖,也不能執行程序。同樣,如果消費者釋放鎖給定的條件和生產者獲得鎖給定的條件不一致的話,則生產者也無法獲得鎖,程序也不能執行。

pthread_mutex

POSIX 互斥鎖是一種超級易用的互斥鎖,使用的時候,只需要初始化一個 pthread_mutex_t 用 pthread_mutex_lock 來鎖定 pthread_mutex_unlock 來解鎖,當使用完成后,記得調用 pthread_mutex_destroy 來銷毀鎖。

pthread_rwlock

讀寫鎖,在對文件進行操作的時候,寫操作是排他的,一旦有多個線程對同一個文件進行寫操作,后果不可估量,但讀是可以的,多個線程讀取時沒有問題的。

當讀寫鎖被一個線程以讀模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程還可以繼續進行。

當讀寫鎖被一個線程以寫模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程也被阻塞。

POSIX Conditions

POSIX 條件鎖需要互斥鎖和條件兩項來實現,雖然看起來沒什么關系,但在運行時中,互斥鎖將會與條件結合起來。線程將被一個互斥和條件結合的信號來喚醒。

首先初始化條件和互斥鎖,當 ready_to_go 為 flase 的時候,進入循環,然后線程將會被掛起,直到另一個線程將 ready_to_go 設置為 true 的時候,并且發送信號的時候,該線程會才被喚醒。

OSSpinLock

自旋鎖,和互斥鎖類似,都是為了保證線程安全的鎖。但二者的區別是不一樣的,對于互斥鎖,當一個線程獲得這個鎖之后,其他想要獲得此鎖的線程將會被阻塞,直到該鎖被釋放。但自選鎖不一樣,當一個線程獲得鎖之后,其他線程將會一直循環在哪里查看是否該鎖被釋放。所以,此鎖比較適用于鎖的持有者保存時間較短的情況下。

然而,YYKit 作者 @ibireme 的文章也有說這個自旋鎖存在優先級反轉問題,具體文章可以戳 不再安全的 OSSpinLock。

os_unfair_lock

自旋鎖已經不在安全,然后蘋果又整出來個 os_unfair_lock_t (╯‵□′)╯︵┻━┻

這個鎖解決了優先級反轉問題。

@synchronized

一個便捷的創建互斥鎖的方式,它做了其他互斥鎖所做的所有的事情。

如果你在不同的線程中傳過去的是一樣的標識符,先獲得鎖的會鎖定代碼塊,另一個線程將被阻塞,如果傳遞的是不同的標識符,則不會造成線程阻塞。

總結

應當針對不同的操作使用不同的鎖,而不能一概而論那種鎖的加鎖解鎖速度快。

當進行文件讀寫的時候,使用 pthread_rwlock 較好,文件讀寫通常會消耗大量資源,而使用互斥鎖同時讀文件的時候會阻塞其他讀文件線程,而 pthread_rwlock 不會。

當性能要求較高時候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保證線程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前兩個是比較好的選擇。既可以保證速度,又可以保證線程安全。

對于 NSLock 及其子類,速度來說 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。


- (void)viewDidLoad

{

? ? ? ? ? [super viewDidLoad];

? ? ? ? ? NSLog(@"1");

? ? ? ? ? dispatch_sync(dispatch_get_main_queue(), ^{

? ? ? ? ? ? ? ? ? ?NSLog(@"2");

? ? ? ? ? });

? ? ? ? ? NSLog(@"3");

} ? ?輸出 1 后 會造成死鎖。

原因? dispatch_sync? 是同步線程執行代碼 要阻塞當前線程,如果當前線程是主線程,首先被阻塞,然后執行輸出,問題是代碼的參數是dispatch_get_main_queue() 它會獲取主線程然后在主線程執行輸出 主線程本來就被阻塞 所以造成了互鎖。(如果當前調用dispatch_sync的是主隊列的話會死鎖,否則不會。因為dispatch_sync會阻塞當前線程等待block執行完畢然后返回,但是當前的block加入到了當前的隊列等待dispatch_sync的執行完畢,那么這么互相依賴顯然死鎖)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在日常開發過程中,為了提升程序運行效率,以及用戶體驗,我們經常使用多線程。在使用多線程的過程中,難免會遇到資源競爭...
    xiao333ma閱讀 1,941評論 1 2
  • 在日常開發過程中,為了提升程序運行效率,以及用戶體驗,我們經常使用多線程。在使用多線程的過程中,難免會遇到資源競爭...
    知識小集閱讀 5,783評論 8 61
  • 鎖是一種同步機制,用于多線程環境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,553評論 0 6
  • 《湖邊兇殺案》這部作品的名字不能再直白了,地點:湖邊,事件:兇殺案。往往是推理小說家處心積慮的盡量拖延讀者接觸核心...
    九月城池閱讀 1,207評論 1 1
  • 召喚師峽谷啊, 我想問問你, 你到底有什么風景? 讓我那么癡迷你的美麗。 召喚師峽谷啊, 我想問問你。 為何每天都...
    小幸運小開心閱讀 360評論 4 0