前言
開發(fā)中引入了異步和多線程的來提高程序性能泡嘴,也就意味著線程安全成為了多線程的一個(gè)障礙甫恩,因此線程鎖應(yīng)運(yùn)而生,而鎖如果用不好酌予,還會(huì)造成死鎖的風(fēng)險(xiǎn)
下面就介紹ios中常用的幾種鎖磺箕,以及讀寫鎖的實(shí)現(xiàn)
常見的多線程鎖
ios中常見的幾種鎖包括OSSpinLock、信號(hào)量(Semaphore)抛虫、pthread_mutex滞磺、NSLock、NSCondition莱褒、NSConditionLock击困、pthread_mutex(recursive)、NSRecursiveLock广凸、synchronized
如下所示阅茶,為前輩們測(cè)試鎖性能的案例圖(實(shí)際可能會(huì)略有偏差):
由于OSSpinLock目前已經(jīng)不再安全,這里就放棄介紹谅海,案例也把他給刪了??
我們?cè)龠x鎖的時(shí)候脸哀,如果只是使用互斥鎖的效果,那么按照性能排序選擇靠前的即可扭吁,如果需要鎖的一些其他功能撞蜂,那么根據(jù)需要選擇盲镶,不必過于局限于性能,畢竟實(shí)現(xiàn)功能與項(xiàng)目的維護(hù)也是非常重要的
其他鎖的使用如下所示
信號(hào)量(semaphore)
信號(hào)量實(shí)現(xiàn)加鎖功能與其他的略有不同蝌诡,其通過一個(gè)信號(hào)值來決定是否阻塞當(dāng)前線程
wait操作可以使得信號(hào)量值減少1溉贿,signal使得信號(hào)量值增加1
當(dāng)wait操作使得信號(hào)量值小于0時(shí),則所在線程阻塞阻塞休眠浦旱,使用signal使得信號(hào)量增加時(shí)宇色,會(huì)順序喚醒阻塞線程,以此便可以實(shí)現(xiàn)加鎖功能,
- (void)semaphore {
_semaphore = dispatch_semaphore_create(1);
}
//wait操作可以使得信號(hào)量值減少1颁湖,signal使得信號(hào)量值增加1
//當(dāng)信號(hào)量值小于0時(shí)宣蠕,則所在線程阻塞休眠,使用signal使得信號(hào)量增加時(shí)甥捺,會(huì)順序喚醒阻塞線程
- (void)semaphoreUpdate {
//wait 可以理解為加鎖操作抢蚀,信號(hào)值小于0會(huì)休眠當(dāng)前wait所在線程
//第二個(gè)參數(shù) forever 為永遠(yuǎn),可以自行設(shè)置一段超時(shí)時(shí)間镰禾,達(dá)到等待時(shí)間會(huì)自動(dòng)解鎖
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
wait和singnal中間的這部分代碼,即為線程安全代碼
_money++;
//signal 可以解鎖
dispatch_semaphore_signal(_semaphore);
}
pthread互斥鎖
pthread互斥鎖是 pthread
庫中的一員皿曲,linux
系統(tǒng)中中常用的庫,使用時(shí)需要手動(dòng)import導(dǎo)入 #import <pthread/pthread.h>
其中有 pthread_mutex_trylock
為嘗試加鎖,如果沒被加鎖羡微,則會(huì)加鎖成功谷饿,并返回0,適用于一些優(yōu)先級(jí)比較低妈倔,間歇性調(diào)用的功能
注意:其他部分鎖也有trylock
這個(gè)功能博投,例如 NSLock、NSRecursiveLock盯蝴、NSConditionLock
#pragma mark --pthread互斥鎖
- (void)pthreadMutex {
pthread_mutex_init(&_pMutexLock, NULL);
//使用完畢后在合適的地方銷毀毅哗,例如dealloc
// pthread_mutex_destroy(&_pMutexLock);
}
- (void)pthreadMutexUpdate {
//加鎖代碼區(qū)間操作,避免多線程同時(shí)訪問
pthread_mutex_lock(&_pMutexLock);
_money++;
//解鎖代碼區(qū)間操作
pthread_mutex_unlock(&_pMutexLock);
}
- (void)pthreadMutexSub {
//減少數(shù)值
[NSThread detachNewThreadWithBlock:^{
//數(shù)量大于100開始減少捧挺,假設(shè)是需要清理東西虑绵,這里減少數(shù)值
while (self->_money > 10000) {
//嘗試加鎖,如果能加鎖闽烙,則加鎖翅睛,返回零,否則返回不為零的數(shù)字
//加鎖失敗休眠在執(zhí)行黑竞,避免搶奪資源捕发,此任務(wù)優(yōu)先級(jí)間接降低
//其他的一些鎖也有這功能,例如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
在一個(gè)時(shí)間之間加鎖,可以理解為加鎖日期截止到指定時(shí)間凡纳,會(huì)自動(dòng)解鎖(與信號(hào)量的等待功能一樣窃植,這個(gè)是設(shè)置到指定時(shí)間)
#pragma mark --NSLock互斥鎖
- (void)NSLock {
_lock = [[NSLock alloc] init];
}
- (void)NSLockUpdate {
//加鎖代碼區(qū)間,避免多線程同時(shí)訪問
[_lock lock];
_money++;
//解鎖代碼區(qū)間
[_lock unlock];
}
NSCondition鎖
NSCondition 算是一個(gè)稍微重量級(jí)的鎖了惫企,我理解為情景鎖
(另一個(gè)原因區(qū)分條件鎖 NSConditionLock
)撕瞧,適用于一些特殊場景陵叽,其也遵循 NSLocking
協(xié)議狞尔,也屬于互斥鎖
并且再其基礎(chǔ)上,新增了信號(hào)量功能 wait
和 signal
巩掺,即 等待 和 釋放 偏序,使用方式和 semaphore
一樣,可以通過信號(hào)量控制線程的阻塞和釋放胖替,除此之外研儒,還多了一個(gè)broadcast
,其可以解除所有因 wait 阻塞的線程
如下所示独令,使用 NSCondition
實(shí)現(xiàn)了一個(gè)生產(chǎn)者和消費(fèi)者的案例(生產(chǎn)者和消費(fèi)者都是同一撥人端朵,因此需要加鎖來實(shí)現(xiàn),而為了保證有錢了立刻買自己想買的東西燃箭,使用信號(hào)量冲呢,保證沒錢時(shí)阻塞等待,有錢時(shí)立即解放買買買)
其相當(dāng)于同時(shí)使用了NSLock 和 Semaphore
功能
#pragma mark --情景鎖NSCondition實(shí)現(xiàn)了NSLocking協(xié)議招狸,支持默認(rèn)的互斥鎖lock敬拓、unlock
- (void)NSCondition {
_condition = [[NSCondition alloc] init];
}
//情景鎖還加入了信號(hào)量機(jī)制,wait和signal,可以利用其完成生產(chǎn)消費(fèi)者模式的功能
//生產(chǎn)者: 媽爸掙了一天的錢裙戏,儲(chǔ)蓄值增加
- (void)conditionPlusMoney {
[_condition lock];
//信號(hào)量增加乘凸,有儲(chǔ)蓄了,可以開放花錢功能了
if (_money++ < 0) {
[_condition signal]; //釋放第一個(gè)阻塞的線程
//[_condition broadcast]; //釋放所有阻塞的線程
}
[_condition unlock];
}
//消費(fèi)者累榜,服務(wù)有儲(chǔ)蓄营勤,拿到錢時(shí)立即解鎖花錢技能(money--)
- (void)conditionSubMoney {
[_condition lock];
if (_money == 0) {
//信號(hào)量減少阻塞,打算買東西壹罚,卻沒錢了葛作,停止花錢,等發(fā)工資再買東西
[_condition wait];
}
//由于之前的wait,當(dāng)signal解鎖后,會(huì)走到這里啸盏,開始購買想買的東西烦味,儲(chǔ)蓄值--
_money--;
[_condition unlock];
}
NSConditionLock
NSConditionLock 被稱為條件鎖,其遵循 NSLocking
協(xié)議屡谐,即具備正常的互斥鎖功能
此外加入了 條件語句续担,為其核心功能赵誓,即滿足指定條件才會(huì)解鎖量瓜,因此算是一個(gè)重量級(jí)的鎖了司恳,其同時(shí)可以理解為 NSCondition 進(jìn)化版
,如果你理解了 NSCondition
的生產(chǎn)者-消費(fèi)者
模式绍傲,這個(gè)也會(huì)馬上就明白了其原理了
lockWhenCondition:(NSInteger)condition
: 加鎖扔傅,當(dāng)條件condition為傳入的condition時(shí),方能解鎖
unlockWithCondition:(NSInteger)condition
: 更新condition的值烫饼,并解鎖指定condition的鎖
下面使用一個(gè)異步隊(duì)列猎塞,來實(shí)現(xiàn)類似 NSOperation 設(shè)置的依賴關(guān)系,如下所示(打印結(jié)果1杠纵、4荠耽、3、2):
#pragma mark --條件鎖NSConditionLock,實(shí)現(xiàn)了NSLocking協(xié)議比藻,支持默認(rèn)的互斥鎖lock铝量、unlock
- (void)NSConditionLock {
_conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //可以更改值測(cè)試為0測(cè)試結(jié)果
//加鎖,當(dāng)條件condition為傳入的condition時(shí)银亲,方能解鎖
//lockWhenCondition:(NSInteger)condition
//更新condition的值慢叨,并解鎖指定condition的鎖
//unlockWithCondition:(NSInteger)condition
}
//多個(gè)隊(duì)列執(zhí)行條件鎖
//通過案例可以看出,通過條件鎖conditionLock可以設(shè)置線程依賴關(guān)系
//可以通過GCD設(shè)置一個(gè)具有依賴關(guān)系的任務(wù)隊(duì)列么
- (void)NSConditionLockUpdate {
//創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue =
dispatch_queue_create("測(cè)試NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
if ([self->_conditionLock tryLockWhenCondition:1]) {
NSLog(@"第一個(gè)");
//默認(rèn)初始conditon位1务蝠,所有能走到這里
//然后解鎖后拍谐,并設(shè)置初始值為4,解鎖condition設(shè)定為4的線程
[self->_conditionLock unlockWithCondition:4];
}else {
[self->_conditionLock lockWhenCondition:0];
NSLog(@"第一個(gè)other");
[self->_conditionLock unlockWithCondition:4];
}
});
//由于開始初始化的conditon值為1请梢,所以后面三個(gè)線程都不滿足條件
//鎖定后直到condition調(diào)整為當(dāng)前線程的condition時(shí)方解鎖
dispatch_async(queue, ^{
//condition設(shè)置為3后解鎖當(dāng)前線程
[self->_conditionLock lockWhenCondition:2];
NSLog(@"第二個(gè)");
//執(zhí)行完畢后解鎖赠尾,并設(shè)置condition為1,設(shè)置初始化默認(rèn)值毅弧,以便于下次使用
[self->_conditionLock unlockWithCondition:1];
});
dispatch_async(queue, ^{
//condition設(shè)置為3后解鎖當(dāng)前線程
[self->_conditionLock lockWhenCondition:3];
NSLog(@"第三個(gè)");
//執(zhí)行完畢后解鎖气嫁,并設(shè)置condition為3,解鎖3
[self->_conditionLock unlockWithCondition:2];
});
dispatch_async(queue, ^{
//condition設(shè)置為4后解鎖當(dāng)前線程
[self->_conditionLock lockWhenCondition:4];
NSLog(@"第四個(gè)");
//執(zhí)行完畢后解鎖够坐,并設(shè)置condition為3寸宵,解鎖3
[self->_conditionLock unlockWithCondition:3];
});
}
上面的流程可以大致簡化為下面幾步:
1.創(chuàng)建一個(gè)異步隊(duì)列,以便于添加后續(xù)的任務(wù)依賴
2.逐步添加子任務(wù)模塊元咙,分別在不同線程中梯影,其有明確的依賴關(guān)系,即執(zhí)行順序?yàn)?1庶香、4甲棍、3、2
3.使用 lockWhenCondition:
開始設(shè)置依賴赶掖,將其任務(wù)解鎖的條件condition
設(shè)置為其特有的condition 號(hào)
感猛,以便于解鎖
4.執(zhí)行任務(wù)時(shí)七扰,如果 NSCondition 中的 condition 參數(shù)
,與本線程設(shè)置的tCondition
不一樣時(shí)陪白,阻塞線程颈走,等待 NSCondition 中的 condition
更改為指定值(通過 unlockWithCondition:
更改condition值)解鎖
即:默認(rèn)初始化 condition 為 1,只有 任務(wù)1
能夠執(zhí)行咱士,當(dāng) 任務(wù)1
執(zhí)行 unlockWithCondition:4
時(shí)立由,condition被設(shè)置為4, 阻塞的任務(wù)4
解鎖,同理序厉,任務(wù)4
執(zhí)行完畢后锐膜,將 condition 設(shè)置為 3 ,任務(wù)三解鎖,依次類推
5.最終根據(jù)設(shè)置的依賴關(guān)系脂矫,分別執(zhí)行 任務(wù)1枣耀、任務(wù)4霉晕、任務(wù)3庭再、任務(wù)2
pthread_mutex(recursive)
其為基于 pthread框架
的遞歸鎖,也是以 pthread互斥鎖為基礎(chǔ)
實(shí)現(xiàn)的 遞歸鎖
牺堰,即:同一個(gè)線程下拄轻,遞歸調(diào)用時(shí)加鎖,不會(huì)阻塞當(dāng)前線程伟葫,當(dāng)另一個(gè)線程到來時(shí)恨搓,會(huì)因?yàn)榈谝粋€(gè)線程加的鎖而阻塞
#pragma mark --pthread遞歸鎖
- (void)pthreadMutexRecursive {
//初始化鎖的遞歸功能
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//互斥鎖初始化時(shí),綁定遞歸鎖功能模塊
pthread_mutex_init(&_pMutexLock, &attr);
//使用完畢后在合適的地方銷毀筏养,例如dealloc
// pthread_mutexattr_destroy(&attr);
// pthread_mutex_destroy(&_pMutexLock);
}
//使用遞歸鎖斧抱,遞歸地時(shí)候回不停加鎖,如果使用普通的鎖早已經(jīng)形成死鎖渐溶,無法解脫
//遞歸鎖的存在就是在同一個(gè)線程中的鎖辉浦,不會(huì)互斥,只會(huì)互斥其他線程的鎖茎辐,從而避免死鎖
- (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)
一樣如下所示
//使用遞歸鎖,遞歸地時(shí)候回不停加鎖依啰,如果使用普通的鎖早已經(jīng)形成死鎖乎串,無法解脫
//遞歸鎖的存在就是在同一個(gè)線程中的鎖,不會(huì)互斥速警,只會(huì)互斥其他線程的鎖叹誉,從而避免死鎖
- (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);
});
}
復(fù)制代碼
synchronized
synchronized 同步鎖桂对,即同步執(zhí)行甩卓,以此避免多線程同時(shí)操作同一塊代碼,基本上在各個(gè)平臺(tái)都會(huì)有其身影蕉斜,雖然效率最低逾柿,但由于使用使用簡單,深得大家喜愛
實(shí)現(xiàn)如下所示
#pragma mark --同步鎖synchronized
- (void)synchronized {
//使用簡單宅此,直接對(duì)代碼塊加同步鎖机错,此代碼不會(huì)被多個(gè)線程直接執(zhí)行
//可以間接理解為里面的任務(wù)被放到了一個(gè)同步隊(duì)列依次執(zhí)行(實(shí)際實(shí)現(xiàn)未知)
@synchronized (self) {
self->_money++;
}
}
讀寫鎖
讀寫鎖
又被稱為 rw鎖
或者 readwrite鎖
,在 ios開發(fā)中雖能見到父腕,但確不是最常用的(一般是數(shù)據(jù)庫操作才會(huì)用到)弱匪。
具體操作為:多讀單寫
,即璧亮,寫入操作只能串行執(zhí)行萧诫,且寫入時(shí),不能讀取枝嘶,而讀取需支持多線程操作帘饶,且讀取時(shí),不能寫入
相信大家也遇到過這樣的事群扶,系統(tǒng)的屬性設(shè)置了 auto
參數(shù)及刻,字面意思為原子性操作,其實(shí)際未能保證屬性字段的多線程安全(由于舊值的賦值未加鎖竞阐,同時(shí)寫入時(shí)缴饭,會(huì)造成對(duì)象舊地址多次被release)
因此無論是想了解其實(shí)現(xiàn)方式,還是開發(fā)備用骆莹,都是有比較學(xué)習(xí)的
實(shí)現(xiàn)方式這里就提供兩種:pthread颗搂、GCD的barrier來實(shí)現(xiàn)
pthread讀寫鎖
使用前,需要先導(dǎo)入 pthread框架
, 即 #import <pthread/pthread.h>
實(shí)現(xiàn)簡單汪疮,可以根據(jù)自己程序需要峭火,選擇鎖初始化的合適位置
//初始化pthread讀寫鎖
- (void)setupPhreadRW {
pthread_rwlock_init(&_lock, NULL);
//使用完畢銷毀讀寫鎖
//pthread_rwlock_destroy(&_lock);
}
#pragma mark --通過pthread讀寫鎖來設(shè)置
- (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柵欄功能相信大家都聽說過,即在一個(gè)新創(chuàng)建的隊(duì)列中智嚷,barrier功能可以保證卖丸,在他之前的異步隊(duì)列執(zhí)行完畢才指定barrier中間的內(nèi)容,且還能保證barrier執(zhí)行完畢后盏道,才之后barrier之后的任務(wù)稍浆,且一個(gè)隊(duì)列可以有多個(gè)barrier
因此此特性可以用于完成一個(gè)讀寫鎖功能,即 barrier的代碼塊作為 寫入操作模塊
如下代碼所示,由于需要引入 新創(chuàng)建隊(duì)列衅枫,雖然使用起來不是不如pthread優(yōu)秀嫁艇,但這種思想?yún)s可以再恰當(dāng)?shù)臅r(shí)候發(fā)芽出新樹苗
- (void)setupGCDRW {
_queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT);
}
#pragma mark --通過GCD的barrier柵欄功能實(shí)現(xiàn)
//通過GCD的barrier柵欄功能實(shí)現(xiàn),缺點(diǎn)是需要借助自定義隊(duì)列實(shí)現(xiàn)弦撩,且get方法無法重寫系統(tǒng)的步咪,只能以回調(diào)的方式獲取值
//barrier功能使用global隊(duì)列會(huì)失效,全局隊(duì)列是無法阻塞的益楼,里面有系統(tǒng)的一些任務(wù)執(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)有的鎖感凤,再寫出一個(gè)完整的讀寫鎖功能出來么!