前言
??在使用多線程的時(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):
- 自旋鎖的性能高于互斥鎖董栽,因?yàn)轫憫?yīng)速度快
- 自旋鎖雖然會(huì)一直自旋等待獲取鎖,但不會(huì)一直占用
CPU
企孩,超過了操作系統(tǒng)分配的時(shí)間片會(huì)被強(qiáng)制掛起- 自旋鎖如果不能保證所有線程都是同一優(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_mutex
是c
底層的線程鎖颠通,關(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_t
的PTHREAD_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 條件鎖
條件鎖它呀,也是對mute
和pthread_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é)完成鎖的介紹:
所有的鎖基本都是創(chuàng)建鎖脊僚、加鎖相叁、等待、解鎖的流程辽幌,所以并不復(fù)雜增淹。
如果追求鎖的極致性能,可以考慮更偏底層實(shí)現(xiàn)的
pthread_mutex
互斥鎖以及信號量的方式乌企。@synchronized
的效率最低虑润,但是它使用最方便,所以如果沒有性能瓶頸的話使用它也不錯(cuò)加酵。
- 什么情況使用自旋鎖比較劃算
- 預(yù)計(jì)線程等待鎖的時(shí)間很短
- 加鎖的代碼(臨界區(qū))會(huì)經(jīng)常調(diào)用拳喻,但競爭情況很少發(fā)生。
-
CPU
資源不緊張 - 多核處理器
- 什么情況使用互斥鎖比較劃算
- 預(yù)計(jì)線程等待鎖的時(shí)間比較長
- 單核處理器猪腕,可以休眠
- 臨界區(qū)有IO(文件讀热叱骸)操作
- 臨界區(qū)代碼比較復(fù)雜,或者循環(huán)量大
- 競爭非常激烈陋葡。