一、基本概念
iOS中的鎖主要可以分為兩大類,互斥鎖 和 自旋鎖,其他鎖都是這兩種鎖的延伸和擴(kuò)展。
1、介紹
互斥鎖:屬于sleep-waiting類型的鎖,線程A獲取到鎖,在釋放鎖之前,其他線程都獲取不到鎖。互斥鎖也分為兩種:
- 遞歸鎖:可重入鎖,同一個(gè)線程在鎖釋放前可再次獲取鎖,即可以遞歸調(diào)用
- 非遞歸鎖:不可重入,必須等鎖釋放后才能再次獲取鎖。
自旋鎖:線程A獲取到鎖,在釋放鎖之前,線程B又來獲取鎖,此時(shí)獲取不到,線程B就會(huì)不斷的進(jìn)入循環(huán),一直檢查鎖是否已被釋放,如果釋放,則能獲取到鎖。
2、區(qū)別
互斥鎖:當(dāng)線程獲取鎖但沒有獲取到時(shí),線程會(huì)進(jìn)入休眠狀態(tài),等鎖被釋放時(shí),線程會(huì)被喚醒,同時(shí)獲取到鎖,繼續(xù)執(zhí)行任務(wù),互斥鎖會(huì)改變線程的狀態(tài)。線程從sleep(加鎖)—>running(解鎖)的過程中,有上下文的切換,cpu的搶占,信號(hào)的發(fā)送等開銷。
自旋鎖:當(dāng)線程獲取鎖但沒獲取到時(shí),不會(huì)進(jìn)入休眠,而是一直循環(huán),線程始終處于活躍狀態(tài),不會(huì)改變線程狀態(tài),也就是忙等。線程一直是running(加鎖—>解鎖),死循環(huán)檢測鎖的標(biāo)志位。遞歸調(diào)用自旋鎖一定會(huì)死鎖。
對比:互斥鎖的起始原始開銷要高于自旋鎖,但是基本是一勞永逸,臨界區(qū)持鎖時(shí)間的大小并不會(huì)對互斥鎖的開銷造成影響,而自旋鎖是死循環(huán)檢測,加鎖全程消耗cpu,起始開銷雖然低于互斥鎖,但是隨著持鎖時(shí)間,加鎖的開銷是線性增長。
3、使用場景
- 互斥鎖會(huì)改變線程的狀態(tài),使得內(nèi)核不斷的調(diào)度線程資源,因此效率上比自旋鎖要低很多,不適合使用自旋鎖的場景都使用互斥鎖。
- 自旋鎖在線程的等待過程中是活躍的,避免了進(jìn)程上下文的調(diào)度開銷,因此對于線程只會(huì)阻塞很短時(shí)間的場合是有效的。因此自旋鎖適合用于短時(shí)間內(nèi)的輕量級(jí)鎖定,主要用在臨界區(qū)持鎖時(shí)間非常短且CPU資源不緊張的情況下。
二、iOS 中的鎖
2.1 NSLock
NSLock是「非」遞歸互斥鎖。
NSLocking只定義了加鎖(獲取鎖)-lock,和解鎖(釋放鎖)-unlock兩個(gè)接口。NSLock、NSConditionLock、NSRecursiveLock、NSCondition都實(shí)現(xiàn)了這個(gè)協(xié)議
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
-lock和-unlock必須在相同的線程調(diào)用,也就是說,他們必須在同一個(gè)線程中成對調(diào)用,否則會(huì)產(chǎn)生未知結(jié)果。參考:官方文檔原文
NSLock是使用了pthread_mutex_t封裝的互斥鎖。
2.2 NSCondition
NSCondition也是使用了pthread_mutex_t封裝的互斥鎖,和NSLock中一模一樣,同時(shí)還使用了pthread_cond_t。它和NSLock的區(qū)別是:
-
NSLock在獲取不到鎖的時(shí)候自動(dòng)使線程進(jìn)入休眠,鎖被釋放后線程又自動(dòng)被喚醒 -
NSCondition可以使我們更加靈活的控制線程狀態(tài),在任何需要的時(shí)候使線程進(jìn)入休眠或喚醒它。
1.[condition lock]:一般用于多線程同時(shí)訪問、修改同一個(gè)數(shù)據(jù)源,保證在同一 時(shí)間內(nèi)數(shù)據(jù)源只被訪問、修改一次,其他線程的命令需要在lock 外等待,只到 unlock ,才可訪問
2.[condition unlock]:與lock 同時(shí)使用
3.[condition wait]:讓當(dāng)前線程處于等待狀態(tài)
4.[condition signal]:CPU發(fā)信號(hào)告訴線程不用在等待,可以繼續(xù)執(zhí)行
2.3 NSConditionLock
NSConditionLock條件鎖就是有特定條件的鎖,說白了就是「有條件的互斥鎖」。
- 只讀屬性
condition,保存鎖當(dāng)前的條件(所謂的條件condition就是個(gè)NSInteger) -
-lockWhenCondition:獲取鎖,如果condition與屬性相等,則可以獲得鎖,否則阻塞線程,等待被喚醒 -
-unlockWithCondition:釋放鎖,并修改condition屬性
// 主線程
self.conditionLock = [[NSConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"進(jìn)入線程1");
// 當(dāng) lock.condition = 2 時(shí),能夠獲取到鎖,否則休眠等待
[self.conditionLock lockWhenCondition:2];
NSLog(@"執(zhí)行任務(wù)1");
sleep(1);
[self.lock unlock];
NSLog(@"退出線程1");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"進(jìn)入線程2");
[self.conditionLock lockWhenCondition:1];
NSLog(@"執(zhí)行任務(wù)2");
sleep(5);
// 將 lock.condition 修改為2,線程1就能獲得鎖了
[self.conditionLock unlockWithCondition:2];
NSLog(@"退出線程2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"進(jìn)入線程3");
[self.lock lock];
NSLog(@"執(zhí)行任務(wù)3");
sleep(2);
// 將 lock.condition 修改為1,線程2就能獲得鎖了
[self.conditionLock unlockWithCondition:1];
NSLog(@"退出線程3");
});
結(jié)果
2020-04-24 16:20:44.901816+0800 lock[48829:11985930] 進(jìn)入線程3
2020-04-24 16:20:44.901816+0800 lock[48829:11985929] 進(jìn)入線程2
2020-04-24 16:20:44.901860+0800 lock[48829:11985931] 進(jìn)入線程1
2020-04-24 16:20:44.902052+0800 lock[48829:11985930] 執(zhí)行任務(wù)3
2020-04-24 16:20:46.906596+0800 lock[48829:11985930] 退出線程3
2020-04-24 16:20:46.906618+0800 lock[48829:11985929] 執(zhí)行任務(wù)2
2020-04-24 16:20:51.908623+0800 lock[48829:11985931] 執(zhí)行任務(wù)1
2020-04-24 16:20:51.908629+0800 lock[48829:11985929] 退出線程2
2020-04-24 16:20:52.913340+0800 lock[48829:11985931] 退出線程1
2.4 遞歸鎖NSRecursiveLock
NSRecursiveLock是互斥鎖中的遞歸鎖,可被 同一線程多次獲取,而不會(huì)產(chǎn)生死鎖。什么意思呢,一個(gè)線程已經(jīng)獲得了鎖,開始執(zhí)行受鎖保護(hù)的代碼(鎖還未釋放),如果這段代碼調(diào)用了其他函數(shù),而被調(diào)用的函數(shù)又要獲取這個(gè)鎖,此時(shí)已然可以獲得鎖并正常執(zhí)行,而不會(huì)死鎖。
基本用法:
- (void)testLock{
self.lock = [[NSRecursiveLock alloc] init];
[NSThread detachNewThreadSelector:@selector(testLock1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(testLock3) toTarget:self withObject:nil];
}
- (void)testLock1 {
[self.lock lock];
NSLog(@"testLock1");
[self testLock2];
[self.lock unlock];
NSLog(@"testLock1: unlock");
}
- (void)testLock2 {
[self.lock lock];
NSLog(@"testLock2");
[self.lock unlock];
NSLog(@"testLock2: unlock");
}
- (void)testLock3 {
[self.lock lock];
NSLog(@"testLock3: %@", [NSThread currentThread]);
[self.lock unlock];
NSLog(@"testLock3: unlock");
}
2.5 對象鎖/同步鎖 @synchronized
@synchronized(id)的使用應(yīng)該是較多的,它底層實(shí)現(xiàn)是個(gè)遞歸鎖,不會(huì)產(chǎn)生死鎖,且不需要手動(dòng)去加鎖解鎖,使用起來比較方便
2.6 dispatch_semaphore
信號(hào)量semaphore是一種更高級(jí)的同步機(jī)制,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例。信號(hào)量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步,而不單單是線程間互斥。
信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步
GCD的信號(hào)量是對系統(tǒng)內(nèi)核信號(hào)量的一層封裝,要想更深入的了解,可以去研究一下Linux內(nèi)核的信號(hào)量。