iOS開發(fā)多線程中的鎖

前言

??在使用多線程的時(shí)候多個(gè)線程可能會(huì)訪問同一塊資源蜜猾,這樣就很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全等問題秀菱。解決資源爭用,最直接的想法是引入蹭睡,對并發(fā)讀寫的數(shù)據(jù)進(jìn)行保護(hù)衍菱,保證每次只有一個(gè)線程訪問這一塊資源。
??是最常用的同步工具:一塊公共資源在同一個(gè)時(shí)間只能允許被一個(gè)線程訪問肩豁,比如一個(gè)線程A進(jìn)入加鎖資源之后梦碗,由于已經(jīng)加鎖,另一個(gè)線程B就無法訪問蓖救,只有等待前一個(gè)線程A執(zhí)行完后解鎖洪规,B線程才能訪問加鎖資源。

為什么需要鎖

?以常見的火車站賣票為例循捺,假設(shè)有20張票斩例,有兩個(gè)窗口同時(shí)售票:

- (void)ticketTest{
    self.ticketsCount = 20;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 線程1
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多線程售票
            }
    });
    // 線程2
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多線程售票
            }
    });
}

- (void)sellingTickets{
    NSInteger oldMoney = self.ticketsCount;
    sleep(0.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"當(dāng)前剩余票數(shù)-> %ld", oldMoney);
}

測試得到的結(jié)果為

2019-02-21 17:10:40.861358+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 19
2019-02-21 17:10:40.861358+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 19
2019-02-21 17:10:40.861723+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 18
2019-02-21 17:10:40.861723+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 18
2019-02-21 17:10:40.861851+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 17
2019-02-21 17:10:40.861961+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 16
2019-02-21 17:10:40.861989+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 16
2019-02-21 17:10:40.862066+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 15
2019-02-21 17:10:40.862222+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 14
2019-02-21 17:10:40.863234+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 13
2019-02-21 17:10:40.863958+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 12
2019-02-21 17:10:40.864225+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 11
2019-02-21 17:10:40.864529+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 10
2019-02-21 17:10:40.865159+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 9
2019-02-21 17:10:40.865498+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 8
2019-02-21 17:10:40.865777+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 7
2019-02-21 17:10:40.866747+0800 多線程測試[8167:20874670] 當(dāng)前剩余票數(shù)-> 6
2019-02-21 17:10:40.866970+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 5
2019-02-21 17:10:40.867402+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 4
2019-02-21 17:10:40.867879+0800 多線程測試[8167:20874671] 當(dāng)前剩余票數(shù)-> 3

不加鎖時(shí)很明顯數(shù)據(jù)發(fā)生了混亂。

iOS中都有哪些鎖从橘?

從大的方向講有兩種鎖:

  • 互斥鎖
  • 自旋鎖念赶。

這兩種類型下分別有自己對應(yīng)的鎖:


鎖的分類

互斥鎖和自旋鎖的對比:

這兩種鎖的相同點(diǎn)不必多說,都可以避免多線程訪問同一個(gè)值發(fā)生混亂恰力,重點(diǎn)說一下兩種的不同點(diǎn):

  • 互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了叉谜,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖。一旦被訪問的資源被解鎖踩萎, 則等待資源的線程會(huì)被喚醒

  • 自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了停局,線程會(huì)以死循環(huán)的方式等待鎖,一旦被訪問的資源被解鎖香府, 則等待資源的線程會(huì)立即執(zhí)行

自旋鎖的特點(diǎn):

  1. 自旋鎖的性能高于互斥鎖董栽,因?yàn)轫憫?yīng)速度快
  2. 自旋鎖雖然會(huì)一直自旋等待獲取鎖,但不會(huì)一直占用CPU企孩,超過了操作系統(tǒng)分配的時(shí)間片會(huì)被強(qiáng)制掛起
  3. 自旋鎖如果不能保證所有線程都是同一優(yōu)先級锭碳,則可能造成死鎖。

因?yàn)橐陨系奶攸c(diǎn)勿璃,自旋鎖和互斥鎖也有不同的使用場景:

多核處理器情況下: 如果預(yù)計(jì)線程等待鎖的時(shí)間比較短擒抛,短到比線程兩次切換上下文的時(shí)間還要少的情況下推汽,自旋鎖是更好的選擇。
如果時(shí)間比較長歧沪,則互斥鎖是比較好的選擇民泵。 單核處理器情況下: 不建議使用自旋鎖。

從詳細(xì)來分鎖:

  • @synchronized
  • NSLock 對象鎖
  • NSRecursiveLock遞歸鎖
  • NSConditionLock 條件鎖
  • pthread_mutex 互斥鎖(C語言)
  • dispatch_semaphore 信號量實(shí)現(xiàn)加鎖(GCD
  • OSSpinLock 自旋鎖

鎖的使用

1. OSSpinLock 自旋鎖

實(shí)現(xiàn)機(jī)制:忙等
使用方式:

#import <libkern/OSAtomic.h>
// 初始化 OS_SPINLOCK_INIT默認(rèn)值為 0,在 locked 狀態(tài)時(shí)就會(huì)大于 0槽畔,unlocked狀態(tài)下為 0
OSSpinLock spinLock = OS_SPINLOCK_INIT;
// 加鎖
OSSpinLockLock(&spinLock);
//你需要保護(hù)的操作
    {}
// 解鎖
OSSpinLockUnlock(&spinLock);

但不幸的是OSSpinLock已經(jīng)在iOS10被蘋果棄用栈妆,因?yàn)樗嬖趦?yōu)先級反轉(zhuǎn)的問題,故不再細(xì)講厢钧,只需要知道其大概的原理就OK鳞尔。

優(yōu)先級反轉(zhuǎn):
發(fā)生在低優(yōu)先級線程拿到鎖時(shí),高優(yōu)先級線程進(jìn)入忙等(busy-wait)狀態(tài)早直,消耗大量CPU 時(shí)間寥假,
從而導(dǎo)致低優(yōu)先級線程拿不到 CPU 時(shí)間,也就無法完成任務(wù)并釋放鎖霞扬。

那為什么忙等會(huì)導(dǎo)致低優(yōu)先級線程拿不到時(shí)間片糕韧?

現(xiàn)代操作系統(tǒng)在管理普通線程時(shí),通常采用時(shí)間片輪轉(zhuǎn)算法(Round Robin喻圃,簡稱 RR)萤彩。每個(gè)線程會(huì)

被分配一段時(shí)間片(quantum),通常在 10-100 毫秒左右斧拍。當(dāng)線程用完屬于自己的時(shí)間片以后雀扶,就會(huì)
被操作系統(tǒng)掛起,放入等待隊(duì)列中肆汹,直到下一次被分配時(shí)間片愚墓。

具體的解釋不再安全的 OSSpinLock

2. os_unfair_lock

這是蘋果iOS10之后推出的新的取代OSSpinLock的鎖。雖然是替代OSSpinLock的昂勉,但os_unfair_lock并不是自旋鎖浪册,根據(jù)蘋果的官方文檔可以看到其實(shí)它是一個(gè)互斥鎖。從底層來看岗照,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài)村象,也不是忙等狀態(tài)。

#import <os/lock.h>
//靜態(tài)初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加鎖
os_unfair_lock_lock(&lock);
//嘗試加鎖谴返,加鎖失敗返回NO煞肾,成功返回YES
bool isCanLock = os_unfair_lock_trylock(&lock);
//安全操作部分
    {}
//解鎖
os_unfair_lock_unlock(&lock);

3. dispatch_semaphore 信號量

信號量(semaphore),有時(shí)被稱為信號燈嗓袱,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用习绢。在進(jìn)入一個(gè)關(guān)鍵代碼段之前渠抹,線程必須獲取一個(gè)信號量蝙昙;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號量梧却。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待直到第一個(gè)線程釋放信號量奇颠。

實(shí)現(xiàn)原理:不是使用忙等,而是阻塞線程并睡眠放航,主動(dòng)讓出時(shí)間片烈拒,需要進(jìn)行上下文切換。
相對于 OSSpinLock 來說广鳍,它的優(yōu)勢在于等待時(shí)不會(huì)消耗 CPU 資源荆几。在沒有等待情況出現(xiàn)時(shí), 它的性能比pthread_mutex還要高赊时,但一旦有等待情況出現(xiàn)時(shí)吨铸,性能就會(huì)下降許多。

使用也是非常的簡單又方便祖秒,會(huì)用下面幾個(gè)函數(shù)就可以:

//創(chuàng)建鎖  value必須 >=0诞吱,若傳入為 0 則阻塞線程并等待timeout,時(shí)間到后會(huì)執(zhí)行其后的語句
dispatch_semaphore_create(long value);
//可以理解為 lock,會(huì)使得 signal 值 -1
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//可以理解為 unlock,會(huì)使得 signal 值 +1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore_create(long value);和GCD的group等用法一致竭缝,這個(gè)函數(shù)是創(chuàng)建一個(gè)dispatch_semaphore類型的信號量房维,并且創(chuàng)建的時(shí)候需要指定信號量的大小。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 等待信號量抬纸。如果信號量值為0握巢,那么該函數(shù)就會(huì)一直等待,也就是不返回(相當(dāng)于阻塞當(dāng)前線程)松却,直到該函數(shù)等待的信號量的值大于等于1暴浦,該函數(shù)會(huì)對信號量的值進(jìn)行減1操作,然后返回晓锻。
dispatch_semaphore_signal(dispatch_semaphore_t deem);發(fā)送信號量歌焦。該函數(shù)會(huì)對信號量的值進(jìn)行加1操作。

??通常等待信號量和發(fā)送信號量的函數(shù)是成對出現(xiàn)的砚哆。并發(fā)執(zhí)行任務(wù)時(shí)候独撇,在當(dāng)前任務(wù)執(zhí)行之前,用dispatch_semaphore_wait函數(shù)進(jìn)行等待(阻塞)躁锁,直到上一個(gè)任務(wù)執(zhí)行完畢后且通過dispatch_semaphore_signal函數(shù)發(fā)送信號量(使信號量的值加1)纷铣,dispatch_semaphore_wait函數(shù)收到信號量之后判斷信號量的值大于等于1,會(huì)再對信號量的值減1战转,然后當(dāng)前任務(wù)可以執(zhí)行搜立,執(zhí)行完畢當(dāng)前任務(wù)后,再通過dispatch_semaphore_signal函數(shù)發(fā)送信號量(使信號量的值加1)槐秧,通知執(zhí)行下一個(gè)任務(wù)......如此一來啄踊,通過信號量忧设,就達(dá)到了并發(fā)隊(duì)列中的任務(wù)同步執(zhí)行的要求。

例如:

- (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(3);
        //發(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  解鎖");
    });
}

4. pthread_mutex 跨平臺(tái)的鎖 ,互斥鎖

pthread_mutexc底層的線程鎖颠通,關(guān)于pthread的各種同步機(jī)制可以看pthread的各種同步機(jī)制址晕。

pthread_mutex互斥鎖 實(shí)現(xiàn)原理:不是使用忙等,而是阻塞線程并睡眠顿锰,主動(dòng)讓出時(shí)間片谨垃,需要進(jìn)行上下文切換。

初始化的時(shí)候創(chuàng)建屬性可以選擇鎖類型:

PTHREAD_MUTEX_NORMAL //普通鎖
PTHREAD_MUTEX_RECURSIVE //遞歸鎖硼控,用于遞歸調(diào)用的時(shí)候避免產(chǎn)生死鎖情況刘陶。

使用需導(dǎo)入頭文件:#import <pthread.h>

//定義pthreadmutex鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
/*
  PTHREAD_MUTEX_NORMAL 缺省類型,也就是普通鎖淀歇。當(dāng)一個(gè)線程加鎖以后易核,其余請求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后先進(jìn)先出原則獲得鎖浪默。
  PTHREAD_MUTEX_ERRORCHECK 檢錯(cuò)鎖牡直,如果同一個(gè)線程請求同一個(gè)鎖运吓,則返回 EDEADLK佛点,否則與普通鎖類型動(dòng)作相同塞蹭。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)嵌套情況下的死鎖牌废。
  PTHREAD_MUTEX_RECURSIVE 遞歸鎖,允許同一個(gè)線程對同一個(gè)鎖成功獲得多次姑荷,并通過多次 unlock 解鎖髓抑。
  PTHREAD_MUTEX_DEFAULT 適應(yīng)鎖嘴拢,動(dòng)作最簡單的鎖類型胜榔,僅等待解鎖后重新競爭胳喷,沒有等待隊(duì)列。
*/
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);//定義鎖的屬性
// 初始化(兩種)
//1.普通初始化
//創(chuàng)建鎖
static pthread_mutex_t pLock;
//初始化一個(gè)互斥鎖
pthread_mutex_init(&pLock, &attr);
//銷毀夭织,一定銷毀對應(yīng)的屬性吭露。
pthread_mutexattr_destroy(&attr);
    
//另一種創(chuàng)建鎖的方式,不需要設(shè)置pthread_mutexattr_t屬性的時(shí)候
//2.宏初始化
//static pthread_mutex_t pLock = PTHREAD_MUTEX_INITIALIZER;
    
//申請鎖
pthread_mutex_lock(&pLock);
//安全操作部分
    {}
//釋放鎖
pthread_mutex_unlock(&pLock);
//銷毀
pthread_mutex_destroy(&pLock);   

5. pthread_cond_t 條件鎖

pthread_mutex 還可以創(chuàng)建條件鎖尊惰,提供了和 NSCondition 一樣的條件控制讲竿,初始化互斥鎖同時(shí)使用pthread_cond_init來初始化條件數(shù)據(jù)結(jié)構(gòu)

//條件鎖
pthread_cond_t cond;
//靜態(tài)初始化
//pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
pthread_condattr_t condAttr;
pthread_mutex_t mutex;
const struct timespec abstimel;
// 初始化
pthread_cond_init(&cond, &condAttr);
//1.放開當(dāng)前鎖 2.使當(dāng)前線程進(jìn)入休眠(wait) 3.喚醒后會(huì)再次mutex程加鎖
pthread_cond_wait(&cond, &mutex);
//在time之前等待,之后放開鎖弄屡。
pthread_cond_timedwait(&cond, &mutex, &abstimel);
// 喚醒一個(gè)被wait的線程
pthread_cond_signal(&cond);
// 喚醒所有被wait的線程
pthread_cond_broadcast(&cond);
// 銷毀
pthread_cond_destroy(&cond);

6. pthread_rwlock_t 讀寫鎖

讀寫鎖题禀,(互斥鎖的進(jìn)化)分為讀鎖(rlock)和寫鎖(wlock),可以有多個(gè)線程共同持有讀鎖膀捷,但是寫鎖只能有一個(gè)線程持有迈嘹。

  • 當(dāng)讀寫鎖被一個(gè)線程以讀模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞担孔,讀操作的其他線程還可以繼續(xù)進(jìn)行江锨。
  • 當(dāng)讀寫鎖被一個(gè)線程以寫模式占用的時(shí)候吃警,寫操作的其他線程會(huì)被阻塞糕篇,讀操作的其他線程也被阻塞啄育。
//靜態(tài)初始化讀鎖
//pthread_rwlock_t rLock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlockattr_t rwlock_attr;
pthread_rwlock_t rwlock;
//動(dòng)態(tài)初始化
pthread_rwlockattr_init(&rwlock_attr);
pthread_rwlock_init(&rwlock, &rwlock_attr);

//獲取一個(gè)讀出鎖
pthread_rwlock_rdlock(&rwlock);  
pthread_rwlock_tryrdlock(&rwlock);
//獲取一個(gè)寫入鎖
pthread_rwlock_wrlock(&rwlock); 
pthread_rwlock_trywrlock(&rwlock);
//釋放一個(gè)寫入鎖或者讀出鎖
pthread_rwlock_unlock(&rwlock); 

比如:

__block pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
    
//讀
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    //NSLog(@"線程0:隨眠 1 秒");//還是不打印能直觀些
    sleep(1);
    NSLog(@"線程0:加鎖");
    pthread_rwlock_rdlock(&rwlock);
    NSLog(@"線程0:讀");
    pthread_rwlock_unlock(&rwlock);
    NSLog(@"線程0:解鎖");
});
    
//寫
dispatch_sync(dispatch_get_global_queue(0, 0),^{       
    //NSLog(@"線程1:隨眠 3 秒");
    sleep(3);      
    NSLog(@"線程1:加鎖");
    pthread_rwlock_wrlock(&rwlock);
    NSLog(@"線程1:寫");
    pthread_rwlock_unlock(&rwlock);
    NSLog(@"線程1:解鎖");
});

7. NSLock、NSCondition拌消、NSConditionLock挑豌、NSRecursiveLock

簡介: 都屬于互斥鎖。
NSLock 底層是對 pthread_mutex_t 的封裝.對應(yīng)的參數(shù)是 PTHREAD_MUTEX_NORMAL
NSCondition 底層則是對 pthread_cond_t 的封裝.
NSConditionLock 的底層則是使 NSCondition 實(shí)現(xiàn)的.
NSRecursiveLock 則是對 pthread_mutex_tPTHREAD_MUTEX_RECURSIVE 參數(shù)的封裝墩崩。
實(shí)現(xiàn)原理可以通過 GNUstep 查看
以上都是蘋果對pthread_mutex的封裝氓英,讓鎖的使用更面向?qū)ο罅恕6甲袷?code>NSCopying協(xié)議鹦筹,此協(xié)議中提供了加鎖和解鎖方法铝阐。

  • NSLock 普通對象鎖

實(shí)質(zhì):NSLock 只是在內(nèi)部封裝了一個(gè) pthread_mutex,屬性為 PTHREAD_MUTEX_ERRORCHECK铐拐,它會(huì)損失一定性能換來錯(cuò)誤提示徘键。

//加鎖
- (void)lock;  
//解鎖
- (void)unlock; 
//能加鎖返回 YES 并執(zhí)行加鎖操作,相當(dāng)于 lock遍蟋,反之返回 NO
- (BOOL)tryLock;  
//這個(gè)方法表示會(huì)在傳入的時(shí)間內(nèi)嘗試加鎖吹害,若能加鎖則執(zhí)行加鎖操作并返回 YES,反之返回 NO
- (BOOL)lockBeforeDate:(NSDate *)limit;  

例如:

- (void)NSLockDemo{
    // 初始化鎖
    NSLock *lock = [[NSLock alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程1 嘗試加鎖ing...");
        [lock lock]; // 加鎖虚青,也有tryLock方法
        sleep(3);//睡眠3秒
        NSLog(@"執(zhí)行線程1");
        [lock unlock];// 解鎖
        NSLog(@"線程1解鎖成功");
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程2 嘗試加鎖ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];//超時(shí)時(shí)間之前不能獲得鎖就返回NO
        if (x) {
            NSLog(@"執(zhí)行線程2");
            [lock unlock];
            NSLog(@"線程2解鎖成功");
        }else{
            NSLog(@"線程2加鎖失敗");
        }
    });
}
  • NSCondition 條件鎖

條件鎖它呀,也是對mutepthread_cond_t的封裝“衾澹可以調(diào)用wait 方法纵穿,等待條件成立再執(zhí)行鎖一下的內(nèi)容。signle方法喚醒線程鎖奢人。

- (void)wait;   // 線程等待
- (BOOL)waitUntilDate:(NSDate *)limit;  // 設(shè)置線程等待時(shí)間谓媒,過了這個(gè)時(shí)間就會(huì)自動(dòng)執(zhí)行后面的代碼
- (void)signal; // 喚醒一個(gè)設(shè)置為wait等待的線程
- (void)broadcast;  // 喚醒所有設(shè)置為wait等待的線程,這個(gè)鎖一般用的較少

例如:

- (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é)果為:

2019-03-10 12:09:02.920733+0800 多線程測試[11562:21367582] 線程一  加鎖成功
2019-03-10 12:09:02.921037+0800 多線程測試[11562:21367580] 線程二  加鎖成功
2019-03-10 12:09:02.921173+0800 多線程測試[11562:21367581] 線程三  加鎖成功
2019-03-10 12:09:02.921280+0800 多線程測試[11562:21367581] 線程三   執(zhí)行任務(wù)
2019-03-10 12:09:02.921397+0800 多線程測試[11562:21367581] 線程三  解鎖
2019-03-10 12:09:02.921551+0800 多線程測試[11562:21367582] 線程一   執(zhí)行任務(wù)
2019-03-10 12:09:02.921651+0800 多線程測試[11562:21367582] 線程一  解鎖
2019-03-10 12:09:02.921787+0800 多線程測試[11562:21367580] 線程二   執(zhí)行任務(wù)
2019-03-10 12:09:02.921933+0800 多線程測試[11562:21367580] 線程二  解鎖

從中可以看出來NSCondition 缺點(diǎn):不能保證任務(wù)有序執(zhí)行达传,只能確保執(zhí)行的任務(wù)篙耗。

  • NSConditionLock 條件鎖

是對NSCondition進(jìn)一步封裝,一個(gè)線程獲得了鎖宪赶,其它線程等待宗弯。

//初始化鎖時(shí),指定一個(gè)默認(rèn)的條件
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
//condition與內(nèi)部相同才會(huì)獲取鎖對象并立即返回搂妻,否則阻塞線程直到condition相同
- (void)lockWhenCondition:(NSInteger)condition;
//滿足特定條件Condition蒙保,嘗試著加鎖,返回bool
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
//解鎖成功欲主,并且設(shè)置lock.condition = condition
- (void)unlockWithCondition:(NSInteger)condition;

例如:

- (void)NSConditionLockDemo{
    //初始化邓厕,指定條件為0
    NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //如果條件為0則可以加上鎖
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"線程1");
            //解鎖并且指定條件為1
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失敗");
        }
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //如果條件為3則可以加上鎖
        [cLock lockWhenCondition:3];
        NSLog(@"線程2");
        [cLock unlockWithCondition:2];
    });
    
    //線程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"線程3");
        [cLock unlockWithCondition:3];
    });
}

結(jié)果為:

2019-03-10 18:47:54.994139+0800 多線程測試[18570:21989216] 線程1
2019-03-10 18:47:54.994973+0800 多線程測試[18570:21989215] 線程3
2019-03-10 18:47:54.995135+0800 多線程測試[18570:21989214] 線程2
  • NSRecursiveLock 遞歸鎖

有時(shí)候“加鎖代碼”中存在遞歸調(diào)用逝嚎,遞歸開始前加鎖,遞歸調(diào)用開始后會(huì)重復(fù)執(zhí)行此方法以至于反復(fù)執(zhí)行加鎖代碼最終造成死鎖详恼。
例如:

- (void)recursiveLockTest {
    //創(chuàng)建鎖
    NSLock *lock = [[NSLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            NSLog(@"加鎖%d",value);
            [lock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"休眠%d",value);
                value--;
                TestMethod(value);
            }
            NSLog(@"解鎖%d",value);
            [lock unlock];
        };
        TestMethod(5);
        NSLog(@"結(jié)束");
    });
}

我們發(fā)現(xiàn)"結(jié)束" 永遠(yuǎn)不會(huì)被打印出來补君,其實(shí)lock 先鎖上了,但未執(zhí)行解鎖的時(shí)候昧互,就會(huì)進(jìn)入遞歸的下一層挽铁,而再次請求上鎖,阻塞了該線程敞掘,線程被阻塞了叽掘,自然后面的解鎖代碼不會(huì)執(zhí)行,而形成了死鎖玖雁,這個(gè)時(shí)候可以使用遞歸鎖來解決更扁。
它和 NSLock 的區(qū)別在于,使用遞歸鎖可以在一個(gè)線程中反復(fù)獲取鎖而不造成死鎖赫冬,這個(gè)過程中會(huì)記錄獲取鎖和釋放鎖的次數(shù)浓镜,只有最后兩者平衡鎖才被最終釋放。
例如:

- (void)NSRecursiveLockTest {
    //創(chuàng)建鎖
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            NSLog(@"加鎖%d",value);
            [recursiveLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"休眠%d",value);
                value--;
                TestMethod(value);
            }
            NSLog(@"解鎖%d",value);
            [recursiveLock unlock];
        };
        TestMethod(5);
        NSLog(@"結(jié)束");
    });
}

運(yùn)行結(jié)果為:

2019-02-22 19:10:39.534716+0800 多線程測試[18849:22028091] 加鎖5
2019-03-10 19:10:40.537983+0800 多線程測試[18849:22028091] 休眠5
2019-03-10 19:10:40.538272+0800 多線程測試[18849:22028091] 加鎖4
2019-03-10 19:10:41.543193+0800 多線程測試[18849:22028091] 休眠4
2019-03-10 19:10:41.543514+0800 多線程測試[18849:22028091] 加鎖3
2019-03-10 19:10:42.544768+0800 多線程測試[18849:22028091] 休眠3
2019-03-10 19:10:42.545114+0800 多線程測試[18849:22028091] 加鎖2
2019-03-10 19:10:43.545918+0800 多線程測試[18849:22028091] 休眠2
2019-03-10 19:10:43.546192+0800 多線程測試[18849:22028091] 加鎖1
2019-03-1019:10:44.547666+0800 多線程測試[18849:22028091] 休眠1
2019-03-10 19:10:44.547927+0800 多線程測試[18849:22028091] 加鎖0
2019-03-10 19:10:44.548097+0800 多線程測試[18849:22028091] 解鎖0
2019-03-10 19:10:44.548250+0800 多線程測試[18849:22028091] 解鎖0
2019-03-10 19:10:44.548458+0800 多線程測試[18849:22028091] 解鎖1
2019-03-10 19:10:44.548648+0800 多線程測試[18849:22028091] 解鎖2
2019-03-10 19:10:44.548850+0800 多線程測試[18849:22028091] 解鎖3
2019-03-10 19:10:44.549040+0800 多線程測試[18849:22028091] 解鎖4
2019-03-10 19:10:44.549231+0800 多線程測試[18849:22028091] 結(jié)束

下面是對上面介紹的各種鎖的執(zhí)行效率的定性分析(只代表加解鎖的效率面殖,比如執(zhí)行1萬次加解鎖的操作耗費(fèi)的時(shí)間)


性能圖

如上竖哩,就是本次介紹的iOS鎖的知識(shí)。最后以一個(gè)小小的總結(jié)完成鎖的介紹:

  1. 所有的鎖基本都是創(chuàng)建鎖脊僚、加鎖相叁、等待、解鎖的流程辽幌,所以并不復(fù)雜增淹。

  2. 如果追求鎖的極致性能,可以考慮更偏底層實(shí)現(xiàn)的pthread_mutex互斥鎖以及信號量的方式乌企。

  3. @synchronized的效率最低虑润,但是它使用最方便,所以如果沒有性能瓶頸的話使用它也不錯(cuò)加酵。

  1. 什么情況使用自旋鎖比較劃算
  • 預(yù)計(jì)線程等待鎖的時(shí)間很短
  • 加鎖的代碼(臨界區(qū))會(huì)經(jīng)常調(diào)用拳喻,但競爭情況很少發(fā)生。
  • CPU資源不緊張
  • 多核處理器
  1. 什么情況使用互斥鎖比較劃算
  • 預(yù)計(jì)線程等待鎖的時(shí)間比較長
  • 單核處理器猪腕,可以休眠
  • 臨界區(qū)有IO(文件讀热叱骸)操作
  • 臨界區(qū)代碼比較復(fù)雜,或者循環(huán)量大
  • 競爭非常激烈陋葡。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亚亲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捌归,老刑警劉巖肛响,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惜索,居然都是意外死亡特笋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門门扇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雹有,“玉大人偿渡,你說我怎么就攤上這事臼寄。” “怎么了溜宽?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵吉拳,是天一觀的道長。 經(jīng)常有香客問我适揉,道長留攒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任嫉嘀,我火速辦了婚禮炼邀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剪侮。我一直安慰自己拭宁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布瓣俯。 她就那樣靜靜地躺著杰标,像睡著了一般。 火紅的嫁衣襯著肌膚如雪彩匕。 梳的紋絲不亂的頭發(fā)上腔剂,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音驼仪,去河邊找鬼掸犬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绪爸,可吹牛的內(nèi)容都是我干的湾碎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼毡泻,長吁一口氣:“原來是場噩夢啊……” “哼胜茧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤呻顽,失蹤者是張志新(化名)和其女友劉穎雹顺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廊遍,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嬉愧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喉前。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片没酣。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卵迂,靈堂內(nèi)的尸體忽然破棺而出裕便,到底是詐尸還是另有隱情,我是刑警寧澤见咒,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布偿衰,位于F島的核電站,受9級特大地震影響改览,放射性物質(zhì)發(fā)生泄漏下翎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一宝当、第九天 我趴在偏房一處隱蔽的房頂上張望视事。 院中可真熱鬧,春花似錦庆揩、人聲如沸俐东。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犬性。三九已至,卻和暖如春腾仅,著一層夾襖步出監(jiān)牢的瞬間乒裆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工推励, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹤耍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓验辞,卻偏偏與公主長得像稿黄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子跌造,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 鎖是一種同步機(jī)制杆怕,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,507評論 0 6
  • 補(bǔ)充: 可以看到除了OSSpinLock外族购,dispatch_semaphore和pthread_mutex性能是...
    笨坨閱讀 3,498評論 0 10
  • 線程安全是怎么產(chǎn)生的 常見比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量,這個(gè)時(shí)候一定要考慮線程安全和同步陵珍。 - (v...
    幽城88閱讀 647評論 0 0
  • Q:為什么出現(xiàn)多線程寝杖? A:為了實(shí)現(xiàn)同時(shí)干多件事的需求(并發(fā)),同時(shí)進(jìn)行著下載和頁面UI刷新互纯。對于處理器瑟幕,為每個(gè)線...
    幸福相依閱讀 1,570評論 0 2
  • 1. 什么情況下會(huì)有線程隱患? 我們在使用多線程技術(shù)帶來的便利的同時(shí)留潦,也需要考慮下多線程所帶來的隱患只盹。比如,我們可...
    沉江小魚閱讀 806評論 0 11