前言
開發(fā)中引入了異步和多線程的來提高程序性能,也就意味著線程安全成為了多線程的一個障礙寥粹,因此線程鎖應運而生,而鎖如果用不好欧芽,還會造成死鎖的風險
下面就介紹ios中常用的幾種鎖男韧,以及讀寫鎖的實現(xiàn)
作為一個開發(fā)者朴摊,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS開發(fā)交流群:598909442此虑,不管你是大牛還是小白都歡迎入駐 甚纲,分享BAT,阿里面試題、面試經(jīng)驗朦前,討論技術介杆, 大家一起交流學習成長!
常見的多線程鎖
ios中常見的幾種鎖包括OSSpinLock韭寸、信號量(Semaphore)春哨、pthread_mutex、NSLock恩伺、NSCondition赴背、NSConditionLock、pthread_mutex(recursive)晶渠、NSRecursiveLock凰荚、synchronized
如下所示,為前輩們測試鎖性能的案例圖(實際可能會略有偏差):
由于OSSpinLock目前已經(jīng)不再安全褒脯,這里就放棄介紹便瑟,案例也把他給刪了??
我們再選鎖的時候,如果只是使用互斥鎖的效果番川,那么按照性能排序選擇靠前的即可到涂,如果需要鎖的一些其他功能,那么根據(jù)需要選擇爽彤,不必過于局限于性能养盗,畢竟實現(xiàn)功能與項目的維護也是非常重要的
其他鎖的使用如下所示
信號量(semaphore)
信號量實現(xiàn)加鎖功能與其他的略有不同,其通過一個信號值來決定是否阻塞當前線程
wait操作可以使得信號量值減少1适篙,signal使得信號量值增加1
當wait操作使得信號量值小于0時往核,則所在線程阻塞阻塞休眠,使用signal使得信號量增加時嚷节,會順序喚醒阻塞線程聂儒,以此便可以實現(xiàn)加鎖功能,
- (void)semaphore {
_semaphore = dispatch_semaphore_create(1);
}
//wait操作可以使得信號量值減少1,signal使得信號量值增加1
//當信號量值小于0時硫痰,則所在線程阻塞休眠衩婚,使用signal使得信號量增加時,會順序喚醒阻塞線程
- (void)semaphoreUpdate {
//wait 可以理解為加鎖操作效斑,信號值小于0會休眠當前wait所在線程
//第二個參數(shù) forever 為永遠非春,可以自行設置一段超時時間,達到等待時間會自動解鎖
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
wait和singnal中間的這部分代碼,即為線程安全代碼
_money++;
//signal 可以解鎖
dispatch_semaphore_signal(_semaphore);
}
pthread互斥鎖
pthread互斥鎖是 pthread
庫中的一員,linux
系統(tǒng)中中常用的庫,使用時需要手動import導入 #import <pthread/pthread.h>
其中有 pthread_mutex_trylock
為嘗試加鎖奇昙,如果沒被加鎖护侮,則會加鎖成功,并返回0储耐,適用于一些優(yōu)先級比較低羊初,間歇性調用的功能
注意:其他部分鎖也有trylock
這個功能,例如 NSLock什湘、NSRecursiveLock长赞、NSConditionLock
#pragma mark --pthread互斥鎖
- (void)pthreadMutex {
pthread_mutex_init(&_pMutexLock, NULL);
//使用完畢后在合適的地方銷毀,例如dealloc
// pthread_mutex_destroy(&_pMutexLock);
}
- (void)pthreadMutexUpdate {
//加鎖代碼區(qū)間操作闽撤,避免多線程同時訪問
pthread_mutex_lock(&_pMutexLock);
_money++;
//解鎖代碼區(qū)間操作
pthread_mutex_unlock(&_pMutexLock);
}
- (void)pthreadMutexSub {
//減少數(shù)值
[NSThread detachNewThreadWithBlock:^{
//數(shù)量大于100開始減少得哆,假設是需要清理東西,這里減少數(shù)值
while (self->_money > 10000) {
//嘗試加鎖腹尖,如果能加鎖柳恐,則加鎖,返回零热幔,否則返回不為零的數(shù)字
//加鎖失敗休眠在執(zhí)行乐设,避免搶奪資源,此任務優(yōu)先級間接降低
//其他的一些鎖也有這功能,例如NSLock绎巨、NSRecursiveLock近尚、NSConditionLock
if (pthread_mutex_trylock(&self->_pMutexLock) == 0) {
self->_money--;
//解鎖
pthread_mutex_unlock(&self->_pMutexLock);
}else {
[NSThread sleepForTimeInterval:1];
}
}
}];
}
NSLock互斥鎖
NSLock 遵循 NSLocking協(xié)議
,是常見的互斥鎖之一,為 OC 框架中的 API场勤,使用方便戈锻,據(jù)說是 pthread 封裝的鎖
tryLock
方法也是嘗試加鎖,成功返回true和媳,失敗返回false
lockBeforeDate:(NSDate *)limit
在一個時間之間加鎖格遭,可以理解為加鎖日期截止到指定時間,會自動解鎖(與信號量的等待功能一樣留瞳,這個是設置到指定時間)
#pragma mark --NSLock互斥鎖
- (void)NSLock {
_lock = [[NSLock alloc] init];
}
- (void)NSLockUpdate {
//加鎖代碼區(qū)間拒迅,避免多線程同時訪問
[_lock lock];
_money++;
//解鎖代碼區(qū)間
[_lock unlock];
}
NSCondition鎖
NSCondition 算是一個稍微重量級的鎖了,我理解為情景鎖
(另一個原因區(qū)分條件鎖 NSConditionLock
)她倘,適用于一些特殊場景璧微,其也遵循 NSLocking
協(xié)議,也屬于互斥鎖
并且再其基礎上硬梁,新增了信號量功能 wait
和 signal
前硫,即 等待 和 釋放 ,使用方式和 semaphore
一樣荧止,可以通過信號量控制線程的阻塞和釋放屹电,除此之外阶剑,還多了一個broadcast
,其可以解除所有因 wait 阻塞的線程
如下所示嗤详,使用 NSCondition
實現(xiàn)了一個生產(chǎn)者和消費者的案例(生產(chǎn)者和消費者都是同一撥人个扰,因此需要加鎖來實現(xiàn)瓷炮,而為了保證有錢了立刻買自己想買的東西葱色,使用信號量,保證沒錢時阻塞等待娘香,有錢時立即解放買買買)
其相當于同時使用了NSLock 和 Semaphore
功能
#pragma mark --情景鎖NSCondition實現(xiàn)了NSLocking協(xié)議苍狰,支持默認的互斥鎖lock、unlock
- (void)NSCondition {
_condition = [[NSCondition alloc] init];
}
//情景鎖還加入了信號量機制,wait和signal烘绽,可以利用其完成生產(chǎn)消費者模式的功能
//生產(chǎn)者: 媽爸掙了一天的錢淋昭,儲蓄值增加
- (void)conditionPlusMoney {
[_condition lock];
//信號量增加,有儲蓄了安接,可以開放花錢功能了
if (_money++ < 0) {
[_condition signal]; //釋放第一個阻塞的線程
//[_condition broadcast]; //釋放所有阻塞的線程
}
[_condition unlock];
}
//消費者翔忽,服務有儲蓄,拿到錢時立即解鎖花錢技能(money--)
- (void)conditionSubMoney {
[_condition lock];
if (_money == 0) {
//信號量減少阻塞盏檐,打算買東西歇式,卻沒錢了,停止花錢胡野,等發(fā)工資再買東西
[_condition wait];
}
//由于之前的wait材失,當signal解鎖后,會走到這里硫豆,開始購買想買的東西龙巨,儲蓄值--
_money--;
[_condition unlock];
}
NSConditionLock
NSConditionLock 被稱為條件鎖,其遵循 NSLocking
協(xié)議熊响,即具備正常的互斥鎖功能
此外加入了 條件語句旨别,為其核心功能,即滿足指定條件才會解鎖汗茄,因此算是一個重量級的鎖了秸弛,其同時可以理解為 NSCondition 進化版
,如果你理解了 NSCondition
的生產(chǎn)者-消費者
模式剔难,這個也會馬上就明白了其原理了
lockWhenCondition:(NSInteger)condition
: 加鎖胆屿,當條件condition為傳入的condition時,方能解鎖
unlockWithCondition:(NSInteger)condition
: 更新condition的值偶宫,并解鎖指定condition的鎖
下面使用一個異步隊列非迹,來實現(xiàn)類似 NSOperation 設置的依賴關系,如下所示(打印結果1纯趋、4憎兽、3冷离、2):
#pragma mark --條件鎖NSConditionLock,實現(xiàn)了NSLocking協(xié)議,支持默認的互斥鎖lock纯命、unlock
- (void)NSConditionLock {
_conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //可以更改值測試為0測試結果
//加鎖西剥,當條件condition為傳入的condition時,方能解鎖
//lockWhenCondition:(NSInteger)condition
//更新condition的值亿汞,并解鎖指定condition的鎖
//unlockWithCondition:(NSInteger)condition
}
//多個隊列執(zhí)行條件鎖
//通過案例可以看出瞭空,通過條件鎖conditionLock可以設置線程依賴關系
//可以通過GCD設置一個具有依賴關系的任務隊列么
- (void)NSConditionLockUpdate {
//創(chuàng)建并發(fā)隊列
dispatch_queue_t queue =
dispatch_queue_create("測試NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
if ([self->_conditionLock tryLockWhenCondition:1]) {
NSLog(@"第一個");
//默認初始conditon位1,所有能走到這里
//然后解鎖后疗我,并設置初始值為4咆畏,解鎖condition設定為4的線程
[self->_conditionLock unlockWithCondition:4];
}else {
[self->_conditionLock lockWhenCondition:0];
NSLog(@"第一個other");
[self->_conditionLock unlockWithCondition:4];
}
});
//由于開始初始化的conditon值為1,所以后面三個線程都不滿足條件
//鎖定后直到condition調整為當前線程的condition時方解鎖
dispatch_async(queue, ^{
//condition設置為3后解鎖當前線程
[self->_conditionLock lockWhenCondition:2];
NSLog(@"第二個");
//執(zhí)行完畢后解鎖吴裤,并設置condition為1旧找,設置初始化默認值,以便于下次使用
[self->_conditionLock unlockWithCondition:1];
});
dispatch_async(queue, ^{
//condition設置為3后解鎖當前線程
[self->_conditionLock lockWhenCondition:3];
NSLog(@"第三個");
//執(zhí)行完畢后解鎖麦牺,并設置condition為3钮蛛,解鎖3
[self->_conditionLock unlockWithCondition:2];
});
dispatch_async(queue, ^{
//condition設置為4后解鎖當前線程
[self->_conditionLock lockWhenCondition:4];
NSLog(@"第四個");
//執(zhí)行完畢后解鎖,并設置condition為3剖膳,解鎖3
[self->_conditionLock unlockWithCondition:3];
});
}
上面的流程可以大致簡化為下面幾步:
1.創(chuàng)建一個異步隊列魏颓,以便于添加后續(xù)的任務依賴
2.逐步添加子任務模塊,分別在不同線程中潮秘,其有明確的依賴關系琼开,即執(zhí)行順序為 1、4枕荞、3柜候、2
3.使用 lockWhenCondition:
開始設置依賴,將其任務解鎖的條件condition
設置為其特有的condition 號
躏精,以便于解鎖
4.執(zhí)行任務時渣刷,如果 NSCondition 中的 condition 參數(shù)
,與本線程設置的tCondition
不一樣時矗烛,阻塞線程辅柴,等待 NSCondition 中的 condition
更改為指定值(通過 unlockWithCondition:
更改condition值)解鎖
即:默認初始化 condition 為 1,只有 任務1
能夠執(zhí)行瞭吃,當 任務1
執(zhí)行 unlockWithCondition:4
時碌嘀,condition被設置為4, 阻塞的任務4
解鎖,同理歪架,任務4
執(zhí)行完畢后股冗,將 condition 設置為 3 ,任務三解鎖,依次類推
5.最終根據(jù)設置的依賴關系和蚪,分別執(zhí)行 任務1止状、任務4烹棉、任務3、任務2
pthread_mutex(recursive)
其為基于 pthread框架
的遞歸鎖怯疤,也是以 pthread互斥鎖為基礎
實現(xiàn)的 遞歸鎖
浆洗,即:同一個線程下,遞歸調用時加鎖集峦,不會阻塞當前線程伏社,當另一個線程到來時,會因為第一個線程加的鎖而阻塞
#pragma mark --pthread遞歸鎖
- (void)pthreadMutexRecursive {
//初始化鎖的遞歸功能
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//互斥鎖初始化時少梁,綁定遞歸鎖功能模塊
pthread_mutex_init(&_pMutexLock, &attr);
//使用完畢后在合適的地方銷毀洛口,例如dealloc
// pthread_mutexattr_destroy(&attr);
// pthread_mutex_destroy(&_pMutexLock);
}
//使用遞歸鎖,遞歸地時候回不停加鎖凯沪,如果使用普通的鎖早已經(jīng)形成死鎖,無法解脫
//遞歸鎖的存在就是在同一個線程中的鎖买优,不會互斥妨马,只會互斥其他線程的鎖,從而避免死鎖
- (void)pthreadMutexRecursiveUpdate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
pthread_mutex_lock(&self->_pMutexLock);
if (count-- > 0) {
self->_money++;
recursiveBlock(count);
}
pthread_mutex_unlock(&self->_pMutexLock);
};
recursiveBlock(1000);
});
}
NSRecursiveLock遞歸鎖
和 pthread_mutex(recursive)
一樣杀赢,NSRecursiveLock 也是遞歸鎖烘跺,其遵循 NSLocking
協(xié)議,即除了遞歸鎖功能脂崔,還具備正常的互斥鎖功能
使用方式和 pthread_mutex(recursive)
一樣如下所示
//使用遞歸鎖滤淳,遞歸地時候回不停加鎖,如果使用普通的鎖早已經(jīng)形成死鎖砌左,無法解脫
//遞歸鎖的存在就是在同一個線程中的鎖脖咐,不會互斥,只會互斥其他線程的鎖汇歹,從而避免死鎖
- (void)NSRecursiveLockUpdate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
[self->_recursive lock];
//tryLock就不多介紹了屁擅,和Pthread的類似,注意返回值即可
//[self->_recursive tryLock];
if (count-- > 0) {
self->_money++;
recursiveBlock(count);
}
[self->_recursive unlock];
};
recursiveBlock(1000);
});
}
復制代碼
synchronized
synchronized 同步鎖产弹,即同步執(zhí)行派歌,以此避免多線程同時操作同一塊代碼,基本上在各個平臺都會有其身影痰哨,雖然效率最低胶果,但由于使用使用簡單,深得大家喜愛
實現(xiàn)如下所示
#pragma mark --同步鎖synchronized
- (void)synchronized {
//使用簡單斤斧,直接對代碼塊加同步鎖早抠,此代碼不會被多個線程直接執(zhí)行
//可以間接理解為里面的任務被放到了一個同步隊列依次執(zhí)行(實際實現(xiàn)未知)
@synchronized (self) {
self->_money++;
}
}
讀寫鎖
讀寫鎖
又被稱為 rw鎖
或者 readwrite鎖
,在 ios開發(fā)中雖能見到折欠,但確不是最常用的(一般是數(shù)據(jù)庫操作才會用到)贝或。
具體操作為:多讀單寫
吼过,即,寫入操作只能串行執(zhí)行咪奖,且寫入時盗忱,不能讀取,而讀取需支持多線程操作羊赵,且讀取時趟佃,不能寫入
相信大家也遇到過這樣的事,系統(tǒng)的屬性設置了 auto
參數(shù)昧捷,字面意思為原子性操作闲昭,其實際未能保證屬性字段的多線程安全(由于舊值的賦值未加鎖,同時寫入時靡挥,會造成對象舊地址多次被release)
因此無論是想了解其實現(xiàn)方式序矩,還是開發(fā)備用,都是有比較學習的
實現(xiàn)方式這里就提供兩種:pthread跋破、GCD的barrier來實現(xiàn)
pthread讀寫鎖
使用前簸淀,需要先導入 pthread框架
, 即 #import <pthread/pthread.h>
實現(xiàn)簡單,可以根據(jù)自己程序需要毒返,選擇鎖初始化的合適位置
//初始化pthread讀寫鎖
- (void)setupPhreadRW {
pthread_rwlock_init(&_lock, NULL);
//使用完畢銷毀讀寫鎖
//pthread_rwlock_destroy(&_lock);
}
#pragma mark --通過pthread讀寫鎖來設置
- (void)setLock1:(NSString *)lock1 {
pthread_rwlock_wrlock(&_lock);
_lock1 = lock1;
pthread_rwlock_unlock(&_lock);
}
- (NSString *)lock1 {
NSString *lock1 = nil;
pthread_rwlock_rdlock(&_lock);
lock1 = [_lock1 copy]; //copy到新的地址,避免解鎖后拿到舊值
pthread_rwlock_unlock(&_lock);
return lock1;
}
GCD的barrier讀寫鎖
GCD的barrier柵欄功能相信大家都聽說過租幕,即在一個新創(chuàng)建的隊列中,barrier功能可以保證拧簸,在他之前的異步隊列執(zhí)行完畢才指定barrier中間的內(nèi)容劲绪,且還能保證barrier執(zhí)行完畢后,才之后barrier之后的任務盆赤,且一個隊列可以有多個barrier
因此此特性可以用于完成一個讀寫鎖功能贾富,即 barrier的代碼塊作為 寫入操作模塊
如下代碼所示,由于需要引入 新創(chuàng)建隊列弟劲,雖然使用起來不是不如pthread優(yōu)秀祷安,但這種思想?yún)s可以再恰當?shù)臅r候發(fā)芽出新樹苗
- (void)setupGCDRW {
_queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT);
}
#pragma mark --通過GCD的barrier柵欄功能實現(xiàn)
//通過GCD的barrier柵欄功能實現(xiàn),缺點是需要借助自定義隊列實現(xiàn)兔乞,且get方法無法重寫系統(tǒng)的汇鞭,只能以回調的方式獲取值
//barrier功能使用global隊列會失效,全局隊列是無法阻塞的庸追,里面有系統(tǒng)的一些任務執(zhí)行
- (void)setLock2:(NSString *)lock2 {
dispatch_barrier_async(_queue, ^{
self->_lock2 = lock2;
});
}
- (void)getLock2WithBlock:(void(^)(NSString *))block {
dispatch_async(_queue, ^{
block(self->_lock2);
});
}
最后
相信看了這篇文章能給大家?guī)砀嗍肇?/p>
最后霍骄,你能根據(jù)讀寫鎖的特性,利用現(xiàn)有的鎖淡溯,再寫出一個完整的讀寫鎖功能出來么!