iOS開發中自旋和互斥鎖的理解以及所有鎖的性能比較

補充:

可以看到除了?OSSpinLock?外,dispatch_semaphore?和?pthread_mutex?性能是最高的。蘋果在新系統中已經優化了?pthread_mutex?的性能,所以它看上去和?OSSpinLock?差距并沒有那么大了。

可以看到YYKit組件中YYCache 和 YYImageCoder大量使用dispatch_semaphorepthread_mutex這兩個鎖

OSSpinLock 自旋鎖(雖然已經被證明不安全 優先級翻轉),性能最高的鎖。原理很簡單,就是一直?do?while?忙等。它的缺點是當等待時會消耗大量 CPU 資源,所以它不適用于較長時間的任務。對于內存緩存的存取來說,它非常合適。

dispatch_semaphore?是信號量,但當信號總量設為1?時也可以當作鎖來。在沒有等待情況出現時,它的性能比 pthread_mutex 還要高,但一旦有等待情況出現時,性能就會下降許多。相對于 OSSpinLock 來說,它的優勢在于等待時不會消耗 CPU 資源。對磁盤緩存來說,它比較合適。

不存在等待的情況,例如不涉及到磁盤這種文件讀寫,dispatch_semaphore性能很高,如果涉及到的任務等待時間較長,就需要用pthread_mutex(OSSpinLock不安全就可以先不用了)

前言

寫這個主要是整合下看到的資料,某天突然看到一到騰訊的面試題,里面就有一提就是讓我們談談我們自己所認識的iOS中的鎖,自己平時看到的就在RAC中的OSSPinLock,AF一些大型框架里面的disaptch_semaphore,還有就是NSLock和@synchronize這幾個常見的鎖了,用法是很簡單,但是別人讓你談談你自己的理解,因此就有了下面的資料,主要來自于一下幾篇文章

深入理解偽代碼介紹

YY大哥的不安全OSSPinLock

簡書小哥的用法介紹

非常感謝這些大神的資料,對這個知識點有了基本的了解

這里貼兩個面試基礎題目,對后續文章看起來會理解更好一點

QA1:自旋和互斥對比?

自旋鎖和互斥鎖

相同點:都能保證同一時間只有一個線程訪問共享資源。都能保證線程安全。

不同點:

互斥鎖:如果共享數據已經有其他線程加鎖了,線程會進入休眠狀態等待鎖。一旦被訪問的資源被解鎖,則等待資源的線程會被喚醒。

自旋鎖:如果共享數據已經有其他線程加鎖了,線程會以死循環的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的線程會立即執行。

自旋鎖的效率高于互斥鎖。

使用自旋鎖時要注意:

由于自旋時不釋放CPU,因而持有自旋鎖的線程應該盡快釋放自旋鎖,否則等待該自旋鎖的線程會一直在哪里自旋,這就會浪費CPU時間。

持有自旋鎖的線程在sleep之前應該釋放自旋鎖以便其他咸亨可以獲得該自旋鎖。內核編程中,如果持有自旋鎖的代碼sleep了就可能導致整個系統掛起。

? ? 使用任何鎖都需要消耗系統資源(內存資源和CPU時間),這種資源消耗可以分為兩類:

? ? ? ? 1.建立鎖所需要的資源

? ? ? ? 2.當線程被阻塞時所需要的資源

兩種鎖的加鎖原理:

互斥鎖:線程會從sleep(加鎖)——>running(解鎖),過程中有上下文的切換(主動出讓時間片,線程休眠,等待下一次喚醒),cpu的搶占,信號的發送等開銷。

自旋鎖:線程一直是running(加鎖——>解鎖),死循環(忙等 do-while)檢測鎖的標志位,機制不復雜。

介紹

鎖:在計算機科學中,鎖是一種同步機制,用于在存在多線程的環境中實施對資源的訪問限制。你可以理解成它用于排除并發的一種策略!你可以理解為為了防止多線程訪問下資源的搶奪,保持線程同步的方式,以下就是常用的幾個鎖

1.@synchronized?關鍵字加鎖?

2. NSLock?對象鎖?

3. NSCondition ?

4. NSConditionLock?條件鎖?

5. NSRecursiveLock?遞歸鎖?

6. pthread_mutex?互斥鎖(C語言)?

7. dispatch_semaphore?信號量實現加鎖(GCD)?

8. OSSpinLock?自旋鎖?

9.pthread_rwlock

10.POSIX Conditions

11.os_unfair_lock? iOS10之后替代OSSPinLock的鎖,解決了優先級反轉的問題

這幾個比較常見的基本用法可以看這里點擊打開鏈接,這個這里就不介紹了,用法都很簡單

1.OSSPinLock

OSSpinLock 不再安全,主要原因發生在低優先級線程拿到鎖時,高優先級線程進入忙等(busy-wait)狀態,消耗大量 CPU 時間,從而導致低優先級線程拿不到 CPU 時間,也就無法完成任務并釋放鎖。這種問題被稱為優先級反轉。

為什么忙等會導致低優先級線程拿不到時間片?這還得從操作系統的線程調度說起。

現代操作系統在管理普通線程時,通常采用時間片輪轉算法(Round Robin,簡稱 RR)。每個線程會被分配一段時間片(quantum),通常在 10-100 毫秒左右。當線程用完屬于自己的時間片以后,就會被操作系統掛起,放入等待隊列中,直到下一次被分配時間片。

忙等這種自旋鎖的實現原理


在 Acquire Lock 這一步,我們申請加鎖,目的是為了保護臨界區(Critical Section) 中的代碼不會被多個線程執行。


上面的偽代碼就是實現自旋鎖的基本原理,初始化一個lock的全局變量,一開始是false,while(lock)的意思是,當lock為true的時候,就進行忙等死循環(do-while申請鎖),由于一開始是false,直接退出循環,然后lock鎖上,執行臨界區代碼,也就是這個時候有其他線程訪問,lock已經被鎖上,while循環會一直忙等,處于申請鎖狀態,上一個鎖執行完任務,就會解鎖,這個時候lock變成了false,之前其他線程忙等狀態下的條件變了,跳出循環,下一個線程執行lock=true,進門執行任務,其他線程繼續等待。這里有個問題,如果一開始有多個線程同時執行 while 循環,他們都不會在這里卡住,而是繼續執行,這樣就無法保證鎖的可靠性了。解決思路也很簡單,只要確保申請鎖的過程是原子操作即可。

用一個原子性操作?test_and_set?來完成,可以理解為線程進來的時候通過鎖的上一個狀態在判斷一次,它用偽代碼可以這樣表示:


該段代碼的意思也很清晰,通過傳入開關地址,先用局部變量存儲一開始開關的狀態,然后內部把開關變成開的狀態,但是返回值是給while的,所以返回值就是開關進來沒操作之前的狀態。

以下就是最終版本的自旋鎖的偽代碼:

如果臨界區的執行時間過長,使用自旋鎖不是個好主意。之前我們介紹過時間片輪轉算法,線程在多種情況下會退出自己的時間片。其中一種是用完了時間片的時間,被操作系統強制搶占。除此以外,當線程進行 I/O 操作,或進入睡眠狀態時,都會主動讓出時間片。顯然在 while 循環中,線程處于忙等狀態,白白浪費 CPU 時間,最終因為超時被操作系統搶占時間片。如果臨界區執行時間較長,比如是文件讀寫,這種忙等是毫無必要的。

2.dispatch_semaphore(信號量? 互斥類型)

dispatch_semaphore是GCD用來同步的一種方式,與他相關的共有三個函數,分別是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。

(1)dispatch_semaphore_create的聲明為:

dispatch_semaphore_t dispatch_semaphore_create(long value);

傳入的參數為long,輸出一個dispatch_semaphore_t類型且值為value的信號量。

值得注意的是,這里的傳入的參數value必須大于或等于0,否則dispatch_semaphore_create會返回NULL。

(2)dispatch_semaphore_signal的聲明為:

long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

這個函數會使傳入的信號量dsema的值加1;

(3) dispatch_semaphore_wait的聲明為:

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

這個函數會使傳入的信號量dsema的值減1;這個函數的作用是這樣的,如果dsema信號量的值大于0,該函數所處線程就繼續執行下面的語句,并且將信號量的值減1;如果desema的值為0,那么這個函數就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續向下執行并將信號量減1。如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執行其后語句。

dispatch_semaphore 是信號量,但當信號總量設為 1 時也可以當作鎖來。在沒有等待情況出現時,它的性能比 pthread_mutex 還要高,但一旦有等待情況出現時,性能就會下降許多。相對于 OSSpinLock 來說,它的優勢在于等待時不會消耗 CPU 資源。

信號量實現內部代碼:


首先會把信號量的值減一,并判斷是否大于零。如果大于零,說明不用等待,所以立刻返回。具體的等待操作在lll_futex_wait函數中實現,lll是 low level lock 的簡稱。這個函數通過匯編代碼實現,調用到SYS_futex這個系統調用,使線程進入睡眠狀態,主動讓出時間片,這個函數在互斥鎖的實現中,也有可能被用到。

主動讓出時間片(互斥的做法,休眠)并不總是代表效率高。讓出時間片會導致操作系統切換到另一個線程,這種上下文切換通常需要 10 微秒左右,而且至少需要兩次切換。如果等待時間很短,比如只有幾個微秒,忙等就比線程睡眠更高效。

3.pthread_mutex

互斥鎖的實現原理與信號量非常相似,不是使用忙等,而是阻塞線程并睡眠,需要進行上下文切換。


對于 pthread_mutex 來說,它的用法和之前沒有太大的改變,比較重要的是鎖的類型,可以有?PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE等等,具體的特性就不做解釋了,網上有很多相關資料。其中如果lock區域代碼,再次進行加鎖,就會變成遞歸,因此我們要把鎖的屬性變成PTHREAD_MUTEX_RECURSIVE來避免死鎖

互斥鎖的實現

互斥鎖在申請鎖時,調用了pthread_mutex_lock方法,它在不同的系統上實現各有不同,有時候它的內部是使用信號量來實現,即使不用信號量,也會調用到lll_futex_wait函數,從而導致線程休眠。

上文說到如果臨界區很短,忙等的效率也許更高,所以在有些版本的實現中,會首先嘗試一定次數(比如 1000 次)的 testandtest,這樣可以在錯誤使用互斥鎖時提高性能。

另外,由于pthread_mutex有多種類型,可以支持遞歸鎖等,因此在申請加鎖時,需要對鎖的類型加以判斷,這也就是為什么它和信號量的實現類似,但效率略低的原因。

4.NSLock,NSCondition,NSRecursiveLock

這些只是上面幾個的上層封裝,可以看上面提供的文章,這里不展開了

5.@synchronize (玉令天下傳送門

這其實是一個 OC 層面的鎖, 主要是通過犧牲性能換來語法上的簡潔與可讀。

我們知道 @synchronized 后面需要緊跟一個 OC 對象,它實際上是把這個對象當做鎖來使用。這是通過一個哈希表來實現的,OC 在底層使用了一個互斥鎖的數組(你可以理解為鎖池),通過對對象去哈希值來得到對應的互斥鎖。

其實這個哈希表的實現和weak屬性一樣,這里是以對象為key,然后互斥鎖的數組為value,weak屬性是以對象為key,指向該對象的weak指針為數組value,當對象dealloc的時候,調用一系列函數,找到該對象的value數組,把數組指針都置為nil,然后再把key對應的記錄全部清空。

原文:https://blog.csdn.net/deft_mkjing/article/details/79513500

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容