iOS開發(fā)中常用的幾種鎖
簡介:
??操作系統(tǒng)在進行多線程調(diào)度的時候种玛,為了保證多線程安全引入了鎖的機制弦疮,以實現(xiàn)指定代碼或資源在某時間內(nèi)只可以被有限個線程訪問。這里主要介紹iOS開發(fā)中褂痰,使用Objective-C開發(fā)所用到的幾種鎖的用法念颈。
1?iOS開發(fā)中常用的幾種鎖
1.1?????? OSSpinLock 自旋鎖
1.2?????? pthread_mutex
1.3?????? pthread_mutex(recursive)
1.4?????? NSLock
1.5?????? dispatch_semaphore
1.6?????? NSCondition
1.7?????? NSRecursiveLock
1.8?????? NSConditionLock
1.9?????? @synchronized
以上為OC作iOS開發(fā)語言時常用到的鎖,其中pthread_mutex和pthread_mutex(recursive) 是C語言實現(xiàn)的卵慰,來源于遵循POSIX標準的pthread多線程庫沙郭。
2?各個鎖的特點和使用方法以及性能總結(jié)
2.1?OSSpinLock(已被棄用)
OSSpinLock 是一種自旋鎖,也只有加鎖裳朋,解鎖病线,嘗試加鎖三個方法,其中嘗試加鎖是非線程阻塞的∷吞簦可用通過 #import <libkern/OSAtomic.h> 引入并調(diào)用绑莺, 使用示例:
OSSpinLock theLock = OS_SPINLOCK_INIT;
OSSpinLockLock(&theLock);
//要執(zhí)行的代碼
OSSpinLockUnlock(&theLock);
OSSpinlock可能造成死鎖的原因:
OSSpinLock 不再安全,原因是有可能在優(yōu)先級比較低的線程里對共享資源進行加鎖了惕耕,然后高優(yōu)先級的線程搶占了低優(yōu)先級的調(diào)用CPU時間纺裁,導致高優(yōu)先級的線程一直在等待低優(yōu)先級的線程釋放鎖,然而低優(yōu)先級根本沒法搶占高優(yōu)先級的CPU時間赡突。這種情況我們稱作 優(yōu)先級倒轉(zhuǎn)对扶。
2.2?pthread_mutex?和?pthread_mutex(recursive)
pthread_mutex表示互斥鎖, 當鎖被占用,而其他線程申請鎖時惭缰,不是使用忙等浪南,而是阻塞線程并睡眠。將線程從睡眠狀態(tài)中喚醒也是比較耗費內(nèi)存資源
示例:
pthread_mutexattr_t attr;?
pthread_mutexattr_init(&attr);?
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);? // 定義鎖的屬性
pthread_mutex_t mutex;?
pthread_mutex_init(&mutex, &attr) // 創(chuàng)建鎖
pthread_mutex_lock(&mutex); // 申請鎖
//線程安全區(qū)域
pthread_mutex_unlock(&mutex);?// 釋放鎖
pthread_mutex(recursive)是遞歸鎖漱受,也就是允許一個線程遞歸的申請鎖络凿,只要把 attr 的類型改成 PTHREAD_MUTEX_RECURSIVE 即可.
2.3?NSLock
NSLock是非遞歸鎖,當同一線程重復獲取同一非遞歸鎖時昂羡,就會發(fā)生死鎖
http://www.reibang.com/p/6116ec8a5595
原因如下:由于當前線程運行到第一個lock加鎖絮记,現(xiàn)在再次運行到lock同樣的鎖,需等待當前線程解鎖虐先,把當前線程掛起怨愤,不能解鎖
NSLock是非遞歸鎖,當同一線程重復獲取同一非遞歸鎖時蛹批,就會發(fā)生死鎖
解決辦法:
我們可以用NSRecursiveLock或者@synchronized替代NSLock
因為NSRecursiveLock或者@synchronized都是遞歸鎖撰洗,
遞歸鎖:它允許同一線程多次加鎖,而不會造成死鎖腐芍。
NSLock 是OC以對象的形式暴露給開發(fā)者的一種鎖差导,它的實現(xiàn)非常簡單,通過宏猪勇,定義了 lock 方法:
#define??? MLOCK \
- (void) lock\
{\
? int err = pthread_mutex_lock(&_mutex);\
? // 錯誤處理 ……
}
NSLock 只是在內(nèi)部封裝了一個 pthread_mutex设褐,屬性為 PTHREAD_MUTEX_ERRORCHECK,它會損失一定性能換來錯誤提示泣刹。
這里使用宏定義的原因是助析,OC 內(nèi)部還有其他幾種鎖,他們的 lock 方法都是一模一樣椅您,僅僅是內(nèi)部 pthread_mutex 互斥鎖的類型不同外冀。通過宏定義,可以簡化方法的定義襟沮。
NSLock 比 pthread_mutex 略慢的原因在于它需要經(jīng)過方法調(diào)用,同時由于緩存的存在,多次方法調(diào)用不會對性能產(chǎn)生太大的影響开伏。
OSSpinLock 和 NSlock的比較
NSLock 請求加鎖失敗的話膀跌,會先輪詢,但一秒過后便會使線程進入 waiting 狀態(tài)固灵,等待喚醒捅伤。而 OSSpinLock 會一直輪詢,等待時會消耗大量 CPU 資源巫玻,不適用于較長時間的任務(wù)丛忆。
2.4?dispatch_semaphore
dispatch_semaphore 是 GCD 使用信號量控制并發(fā),相比較OSSpinLock等待狀態(tài)消耗 CPU 資源仍秤。?相關(guān)的三個函數(shù):
1.創(chuàng)建信號量熄诡,
2.等待信號
3.發(fā)送信號
dispatch_semaphore_create(long value); dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
?
當設(shè)置信號量為 1 時,一個 dispatch_semaphore_wait(signal, overTime); 方法對應一個 dispatch_semaphore_signal(signal); 類似NSLock 的 lock 和 unlock诗力,區(qū)別在于有信號量這個參數(shù)凰浮,lock unlock 只能同一時間,一個線程訪問被保護的臨界區(qū)苇本,而如果 dispatch_semaphore 的信號量初始值為 x 袜茧,則可以有 x 個線程同時訪問被保護的臨界區(qū),即可以控制多個線程并發(fā)瓣窄。
?
2.5?NSCondition?
NSCondition?的底層是通過條件變量(condition variable)?pthread_cond_t?來實現(xiàn)的笛厦。條件變量有點像信號量,提供了線程阻塞與信號機制俺夕,因此可以用來阻塞某個線程裳凸,并等待某個數(shù)據(jù)就緒,隨后喚醒線程啥么,比如常見的生產(chǎn)者-消費者模式登舞。
示例:
??? ????NSCondition *lock = [[NSCondition alloc] init];
??? //線程1
??????? [lock lock];
??????? [lock wait]; // 線程被掛起
??????? [lock unlock];
??? //線程2
??????? sleep(1);//以保證讓線程2的代碼后執(zhí)行
??????? [lock lock];
??????? [lock signal]; // 喚醒線程1
??????? [lock unlock];
2.6?NSRecursiveLock
遞歸鎖也是通過?pthread_mutex_lock?函數(shù)來實現(xiàn),在函數(shù)內(nèi)部會判斷鎖的類型悬荣,如果顯示是遞歸鎖菠秒,就允許遞歸調(diào)用,僅僅將一個計數(shù)器加一氯迂,鎖的釋放過程也是同理沟使。
NSRecursiveLock?與?NSLock?的區(qū)別在于內(nèi)部封裝的?pthread_mutex_t?對象的類型不同,前者的類型為?PTHREAD_MUTEX_RECURSIVE判哥。
2.7?NSConditionLock
NSConditionLock 可以稱為條件鎖忱叭,只有 condition 參數(shù)與初始化時候的 condition 相等,lock 才能正確進行加鎖操作轿曙。而 unlockWithCondition: 并不是當 Condition 符合條件時才解鎖弄捕,而是解鎖之后僻孝,修改 Condition 的值。
NSConditionLock 借助 NSCondition 來實現(xiàn)守谓,它的本質(zhì)就是一個生產(chǎn)者-消費者模型穿铆。“條件被滿足”可以理解為生產(chǎn)者提供了新的內(nèi)容斋荞。NSConditionLock 的內(nèi)部持有一個 NSCondition 對象荞雏,以及 _condition_value 屬性,在初始化時就會對這個屬性進行賦值:
// 簡化版代碼
- (id) initWithCondition: (NSInteger)value {
??? if (nil != (self = [super init])) {
??????? _condition = [NSCondition new]
??????? _condition_value = value;
??? }
??? return self;
}
它的 lockWhenCondition 方法其實就是消費者方法:
- (void) lockWhenCondition: (NSInteger)value {
??? [_condition lock];
??? while (value != _condition_value) {
??????? [_condition wait];
??? }
}
對應的 unlockWhenCondition 方法則是生產(chǎn)者平酿,使用了 broadcast 方法通知了所有的消費者:
- (void) unlockWithCondition: (NSInteger)value {
??? _condition_value = value;
??? [_condition broadcast];
??? [_condition unlock];
}
2.8??@synchronized
這其實是一個 OC 層面的鎖凤优,主要是通過犧牲性能換來語法上的簡潔與可讀。
@synchronized 后面需要緊跟一個 OC 對象蜈彼,它實際上是把這個對象當做鎖的唯一標識筑辨。這是通過一個哈希表來記錄表示,OC 在底層使用了一個互斥鎖的數(shù)組(你可以理解為鎖池)柳刮,通過對對象去哈希值在數(shù)組中得到對應的互斥鎖挖垛。
示例:
@synchronized(self) {
????? //線程安全代碼
}
3?性能對比
下圖通過加鎖耗時簡單的比較了各種鎖的加解鎖性能
?
性能測試源碼:
https://github.com/ibireme/tmp/blob/master/iOSLockBenckmark/iOSLockBenckmark/ViewController.m