iOS 開發(fā)中的鎖(Lock)

背景:最近在項(xiàng)目中涉及到很多鎖方面的知識(shí)号阿,于是在項(xiàng)目完成后圣拄,決定把最近關(guān)于鎖的知識(shí)記錄下來(lái)纽什,方便自己以后查詢

  1. 鎖的相關(guān)概念
  2. 鎖的性能比較
  3. 鎖的用法
  4. 各種鎖的優(yōu)缺點(diǎn)

概念

0.鎖: 語(yǔ)言的并發(fā)程序設(shè)計(jì)馅笙,一直是一個(gè)比較困難且不可避免的話題。多數(shù)程序員都會(huì)嘗試使用多線程編程蔬捷,但是卻很難保證自己所寫的多線程程序的正確性垄提。多線程程序,如果涉及到對(duì)共享資源的并發(fā)讀寫周拐,就會(huì)產(chǎn)生資源爭(zhēng)用铡俐。解決資源爭(zhēng)用,最直接的想法是引入鎖妥粟,對(duì)并發(fā)讀寫的數(shù)據(jù)進(jìn)行保護(hù)审丘。

1.死鎖:死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象勾给,若無(wú)外力作用滩报,它們都將無(wú)法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖播急,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程脓钾。當(dāng)然,產(chǎn)生死鎖有四個(gè)條件桩警,有需要深入了解的我在后文給鏈接

2.互斥鎖:最常使用于線程同步的鎖可训;標(biāo)記用來(lái)保證在任一時(shí)刻,只能有一個(gè)線程訪問(wèn)該對(duì)象,同一線程多次加鎖操作會(huì)造成死鎖沉噩;臨界區(qū)和互斥量都可用來(lái)實(shí)現(xiàn)此鎖捺宗,通常情況下鎖操作失敗會(huì)將該線程睡眠等待鎖釋放時(shí)被喚醒

3.自旋鎖:同樣用來(lái)標(biāo)記只能有一個(gè)線程訪問(wèn)該對(duì)象,在同一線程多次加鎖操作會(huì)造成死鎖川蒙;同互斥鎖不同的是在鎖操作需要等待的時(shí)候并不是睡眠等待喚醒蚜厉,而是循環(huán)檢測(cè)保持者已經(jīng)釋放了鎖,這樣做的好處是節(jié)省了線程從睡眠狀態(tài)到喚醒之間內(nèi)核會(huì)產(chǎn)生的消耗畜眨,在加鎖時(shí)間短暫的環(huán)境下這點(diǎn)會(huì)提高很大效率

4.遞歸鎖:嚴(yán)格上講遞歸鎖只是互斥鎖的一個(gè)特例昼牛,同樣只能有一個(gè)線程訪問(wèn)該對(duì)象,但允許同一個(gè)線程在未釋放其擁有的鎖時(shí)反復(fù)對(duì)該鎖進(jìn)行加鎖操作康聂;

iOS常用鎖的性能比較

這個(gè)問(wèn)題我沒有去測(cè)試贰健,引用一張大神測(cè)試后的照片截圖
各種鎖的性能.png

這是參考式鏈接:
[libireme 不再安全的 OSSpinLock]:
https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

iOS 鎖的用法

OSSpinLock
OSSpinLock 使用方法
  - (void)testOSSpinLock{
    
    __block OSSpinLock oslock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程1  準(zhǔn)備上鎖");
        OSSpinLockLock(&oslock);
        NSLog(@"線程1   執(zhí)行任務(wù)");
        OSSpinLockUnlock(&oslock);
        NSLog(@"線程1  解鎖成功");
        
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"線程2  準(zhǔn)備上鎖");
        OSSpinLockLock(&oslock);
        NSLog(@"線程2  執(zhí)行任務(wù)");
        OSSpinLockUnlock(&oslock);
        NSLog(@"線程2   解鎖成功");
        
    });
}
OSSpinLock 存在問(wèn)題

新版 iOS 中,系統(tǒng)維護(hù)了 5 個(gè)不同的線程優(yōu)先級(jí)/QoS: background恬汁,utility伶椿,default,user-initiated氓侧,user-interactive脊另。高優(yōu)先級(jí)線程始終會(huì)在低優(yōu)先級(jí)線程前執(zhí)行,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)線程的干擾约巷。這種線程調(diào)度算法會(huì)產(chǎn)生潛在的優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題偎痛,從而破壞了 spin lock。
具體來(lái)說(shuō)独郎,如果一個(gè)低優(yōu)先級(jí)的線程獲得鎖并訪問(wèn)共享資源踩麦,這時(shí)一個(gè)高優(yōu)先級(jí)的線程也嘗試獲得這個(gè)鎖,它會(huì)處于 spin lock 的忙等狀態(tài)從而占用大量 CPU氓癌。此時(shí)低優(yōu)先級(jí)線程無(wú)法與高優(yōu)先級(jí)線程爭(zhēng)奪 CPU 時(shí)間谓谦,從而導(dǎo)致任務(wù)遲遲完不成、無(wú)法釋放 lock顽铸。這并不只是理論上的問(wèn)題茁计,libobjc 已經(jīng)遇到了很多次這個(gè)問(wèn)題了,于是蘋果的工程師停用了 OSSpinLock谓松。
蘋果工程師 Greg Parker 提到星压,對(duì)于這個(gè)問(wèn)題,一種解決方案是用 truly unbounded backoff 算法鬼譬,這能避免 livelock 問(wèn)題娜膘,但如果系統(tǒng)負(fù)載高時(shí),它仍有可能將高優(yōu)先級(jí)的線程阻塞數(shù)十秒之久优质;另一種方案是使用 handoff lock 算法竣贪,這也是 libobjc 目前正在使用的军洼。鎖的持有者會(huì)把線程 ID 保存到鎖內(nèi)部,鎖的等待者會(huì)臨時(shí)貢獻(xiàn)出它的優(yōu)先級(jí)來(lái)避免優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題演怎。理論上這種模式會(huì)在比較復(fù)雜的多鎖條件下產(chǎn)生問(wèn)題匕争,但實(shí)踐上目前還一切都好。
libobjc 里用的是 Mach 內(nèi)核的 thread_switch() 然后傳遞了一個(gè) mach thread port 來(lái)避免優(yōu)先級(jí)反轉(zhuǎn)爷耀,另外它還用了一個(gè)私有的參數(shù)選項(xiàng)甘桑,所以開發(fā)者無(wú)法自己實(shí)現(xiàn)這個(gè)鎖。另一方面歹叮,由于二進(jìn)制兼容問(wèn)題跑杭,OSSpinLock 也不能有改動(dòng)。
最終的結(jié)論就是咆耿,除非開發(fā)者能保證訪問(wèn)鎖的線程全部都處于同一優(yōu)先級(jí)德谅,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了。

這是參考式鏈接:
[libireme 不再安全的 OSSpinLock]:
https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

dispatch_semaphore
dispatch_semaphore

信號(hào)量是一個(gè)整形值并且具有一個(gè)初始計(jì)數(shù)值萨螺,并且支持兩個(gè)操作:信號(hào)通知和等待窄做。當(dāng)一個(gè)信號(hào)量被信號(hào)通知,其計(jì)數(shù)會(huì)被增加屑迂。當(dāng)一個(gè)線程在一個(gè)信號(hào)量上等待時(shí)浸策,線程會(huì)被阻塞(如果有必要的話),直至計(jì)數(shù)器大于零惹盼,然后線程會(huì)減少這個(gè)計(jì)數(shù),在GCD中有三個(gè)函數(shù)是semaphore的操作

  • dispatch_semaphore_create(傳入值): 傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout,時(shí)間到后會(huì)執(zhí)行其后的語(yǔ)句

  • dispatch_semaphore_wait(signal, overTime):可以理解為 lock,會(huì)使得 signal 值 -1

  • dispatch_semaphore_signal(signal):可以理解為 unlock,會(huì)使得 signal 值 +1

- (void)dispatchSemaphore{
    
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    
    //等待時(shí)間
    dispatch_time_t overTime = DISPATCH_TIME_FOREVER;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        //等同加鎖 -1
        dispatch_semaphore_wait(signal, overTime);
        
        NSLog(@"線程1  執(zhí)行任務(wù)");

        sleep(10);
        //發(fā)通知  等同解鎖  +1
        dispatch_semaphore_signal(signal);
        
        NSLog(@"線程1  解鎖");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        //等同加鎖 -1
        dispatch_semaphore_wait(signal, overTime);
        
        NSLog(@"線程2 執(zhí)行任務(wù)");
        //發(fā)通知  等同解鎖  +1
        dispatch_semaphore_signal(signal);
        
        NSLog(@"線程2  解鎖");
    });
}
NSCondition

NSCondition 的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器:鎖主要為了當(dāng)檢測(cè)條件時(shí)保護(hù)數(shù)據(jù)源,執(zhí)行條件引發(fā)的任務(wù)惫确;線程檢查器主要是根據(jù)條件決定是否繼續(xù)運(yùn)行線程手报,即線程是否被阻塞。

  • wait:進(jìn)入等待狀態(tài)
  • waitUntilDate::讓一個(gè)線程等待一定的時(shí)間
  • signal:?jiǎn)拘岩粋€(gè)等待的線程
  • broadcast:?jiǎn)拘阉械却木€程
NSCondition 使用方法

假定:有A改化、B......兩個(gè)個(gè)任務(wù)掩蛤,沒有執(zhí)行順序,或者只能確定確定一個(gè)執(zhí)行任務(wù),看下面代碼陈肛,以及執(zhí)行結(jié)果

- (void)testConditionLock{
    
    NSCondition *condition = [[NSCondition alloc] init];
    
    //線程一
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [condition lock];
        NSLog(@"線程一  加鎖成功");
        [condition wait];
        NSLog(@"線程一   執(zhí)行任務(wù)");
        [condition unlock];
        NSLog(@"線程一  解鎖");
         [condition signal];
        
    });
    
    //線程二
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [condition lock];
        NSLog(@"線程二  加鎖成功");
        [condition wait];
        NSLog(@"線程二   執(zhí)行任務(wù)");
        [condition unlock];
         NSLog(@"線程二  解鎖");
         [condition signal];
       
    });
    //線程三
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [condition lock];
        NSLog(@"線程三  加鎖成功");
        //[condition wait];
        NSLog(@"線程三   執(zhí)行任務(wù)");
        [condition unlock];
        NSLog(@"線程三  解鎖");
        [condition signal];
        
        
    });
}

結(jié)果

2018-03-21 17:06:23.156804+0800 Lock-test[74191:6464691] 線程一  加鎖成功
2018-03-21 17:06:23.157058+0800 Lock-test[74191:6464694] 線程二  加鎖成功
2018-03-21 17:06:23.157413+0800 Lock-test[74191:6464693] 線程三  加鎖成功
2018-03-21 17:06:23.158964+0800 Lock-test[74191:6464693] 線程三   執(zhí)行任務(wù)
2018-03-21 17:06:23.159106+0800 Lock-test[74191:6464693] 線程三  解鎖
2018-03-21 17:06:23.159695+0800 Lock-test[74191:6464691] 線程一   執(zhí)行任務(wù)
2018-03-21 17:06:23.159942+0800 Lock-test[74191:6464691] 線程一  解鎖
2018-03-21 17:06:23.160125+0800 Lock-test[74191:6464694] 線程二   執(zhí)行任務(wù)
2018-03-21 17:06:23.160249+0800 Lock-test[74191:6464694] 線程二  解鎖
NSCondition 缺點(diǎn):

不能保證任務(wù)有序執(zhí)行揍鸟,只能確保第一執(zhí)行的任務(wù)

NSConditionLock

條件鎖,一個(gè)線程獲得了鎖句旱,其它線程等待

  • tryLockWhenCondition:表示如果沒有其他線程獲得該鎖阳藻,但是該鎖內(nèi)部的condition不等于條件,它依然不能獲得鎖谈撒,仍然等待

  • unlockWithCondition:: 解鎖成功腥泥,并指向下個(gè)一條件

NSConditionLock 使用方法

假定:有A、B啃匿、C三個(gè)任務(wù)蛔外,需要執(zhí)行的順序是B->A->C

- (void)testConditonLock{
    
    NSConditionLock *condition = [[NSConditionLock alloc] initWithCondition:2];
    
    //線程一
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        
        NSLog(@"線程一 加鎖");
        [condition lockWhenCondition:1];
        NSLog(@"線程一 執(zhí)行任務(wù)");
        [condition unlockWithCondition:3];
       
        
    });
    //線程二
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程二 加鎖");
        if([condition tryLockWhenCondition:2]){
            NSLog(@"線程二 執(zhí)行任務(wù)");
        }else{
            NSLog(@"執(zhí)行任務(wù)失敗");
        }
        
        [condition unlockWithCondition:1];
        
    });
    
    //線程三
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程三 加鎖");
        [condition lockWhenCondition:3];
        NSLog(@"線程三 執(zhí)行任務(wù)");
//        [condition unlockWithCondition:3];
    });
}
2018-03-21 17:23:45.333313+0800 Lock-test[74523:6479604] 線程二 加鎖
2018-03-21 17:23:45.333313+0800 Lock-test[74523:6479607] 線程一 加鎖
2018-03-21 17:23:45.333313+0800 Lock-test[74523:6479605] 線程三 加鎖
2018-03-21 17:23:45.333687+0800 Lock-test[74523:6479604] 線程二 執(zhí)行任務(wù)
2018-03-21 17:23:45.335576+0800 Lock-test[74523:6479607] 線程一 執(zhí)行任務(wù)
2018-03-21 17:23:45.335783+0800 Lock-test[74523:6479605] 線程三 執(zhí)行任務(wù)
NSConditionLock 優(yōu)點(diǎn):

可以在加鎖的同時(shí)蛆楞,保證任務(wù)有序進(jìn)行。缺點(diǎn)可能就是所謂性能低了

synchronized

synchronized 遞歸鎖夹厌,最常見豹爹,也是最常用的鎖。

- (void)testSYnchronized{
    
    //線程一
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(self){
            
           
            NSLog(@"線程一");
            
            [self test:@"=="];
        }
    });
    
    //線程二
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        @synchronized(self){
            
            NSLog(@"線程二");
            [self test:@"================================="];
        }
    });
}

- (void)test:(NSString *)test{
    
    while (1) {
        NSLog(@"%@", test);
    }
}
參考資料

https://juejin.im/entry/5a00f59ff265da4314401967

http://blog.csdn.net/qq_30513483/article/details/52349968

https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

http://ios.jobbole.com/82826/

http://www.cnblogs.com/huangjianwu/p/4575763.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矛纹,一起剝皮案震驚了整個(gè)濱河市帅戒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崖技,老刑警劉巖逻住,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異迎献,居然都是意外死亡瞎访,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門吁恍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扒秸,“玉大人,你說(shuō)我怎么就攤上這事冀瓦“榘拢” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵翼闽,是天一觀的道長(zhǎng)拾徙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)感局,這世上最難降的妖魔是什么尼啡? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮询微,結(jié)果婚禮上崖瞭,老公的妹妹穿的比我還像新娘。我一直安慰自己撑毛,他們只是感情好书聚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藻雌,像睡著了一般雌续。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦疑,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天西雀,我揣著相機(jī)與錄音,去河邊找鬼歉摧。 笑死艇肴,一個(gè)胖子當(dāng)著我的面吹牛腔呜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播再悼,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼核畴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了冲九?” 一聲冷哼從身側(cè)響起谤草,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎莺奸,沒想到半個(gè)月后丑孩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灭贷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年温学,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甚疟。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仗岖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出览妖,到底是詐尸還是另有隱情轧拄,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布讽膏,位于F島的核電站檩电,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桅打。R本人自食惡果不足惜是嗜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挺尾。 院中可真熱鬧,春花似錦站绪、人聲如沸遭铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)魂挂。三九已至,卻和暖如春馁筐,著一層夾襖步出監(jiān)牢的瞬間涂召,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工敏沉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留果正,地道東北人炎码。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秋泳,于是被迫代替她去往敵國(guó)和親潦闲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 鎖是一種同步機(jī)制迫皱,用于多線程環(huán)境中對(duì)資源訪問(wèn)的限制iOS中常見鎖的性能對(duì)比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,521評(píng)論 0 6
  • 在平時(shí)的開發(fā)中經(jīng)常使用到多線程歉闰,在使用多線程的過(guò)程中,難免會(huì)遇到資源競(jìng)爭(zhēng)的問(wèn)題卓起,那我們?cè)趺磥?lái)避免出現(xiàn)這種問(wèn)題那和敬? ...
    IAMCJ閱讀 3,099評(píng)論 2 25
  • 線程安全是怎么產(chǎn)生的 常見比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量,這個(gè)時(shí)候一定要考慮線程安全和同步戏阅。 - (v...
    幽城88閱讀 666評(píng)論 0 0
  • demo下載 建議一邊看文章昼弟,一邊看代碼。 聲明:關(guān)于性能的分析是基于我的測(cè)試代碼來(lái)的饲握,我也看到和網(wǎng)上很多測(cè)試結(jié)果...
    炸街程序猿閱讀 801評(píng)論 0 2
  • 鎖是最常用的同步工具私杜。一段代碼段在同一個(gè)時(shí)間只能允許被有限個(gè)線程訪問(wèn),比如一個(gè)線程 A 進(jìn)入需要保護(hù)代碼之前添加簡(jiǎn)...
    AidenRao閱讀 21,527評(píng)論 28 160