前言
對(duì)于iOS中各種鎖的學(xué)習(xí)總結(jié),供日后查閱
引子
日常開(kāi)發(fā)中,@property (nonatomic, strong) *foo
是我們不厭其煩的使用頻率最高的聲明方式,也很清楚atomic
和nonatomic
屬性的區(qū)別,這里再?gòu)?fù)習(xí)一下這兩個(gè)關(guān)鍵字:
-
atomic
:原子性,這個(gè)屬性是默認(rèn)的祈远,通過(guò)在setter
、getter
中加鎖保證數(shù)據(jù)的讀寫(xiě)安全 -
nonatomic
:非原子性商源,就是不加鎖车份。優(yōu)點(diǎn)是速度優(yōu)于使用atomic
,大多數(shù)場(chǎng)景不會(huì)出現(xiàn)問(wèn)題
作為編譯器標(biāo)識(shí)符牡彻,@property
的作用是幫助我們快速生成成員變量及其getter/setter方法扫沼,并通過(guò)屬性關(guān)鍵字出爹,幫助我們管理內(nèi)存及安全等繁雜的事務(wù),那么atomic
是如何幫助我們保證成員變量的讀寫(xiě)安全呢缎除?下面我們看一段代碼:
//@property(retain) UITextField *userName;
//示例代碼如下:
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [userName retain];
_userName = retval;
}
return retval;
}
- (void) setUserName:(UITextField *)userName {
@synchronized(self) {
[_userName release];
_userName = [userName retain];
}
}
我們可以很容易的看出严就,編譯器是通過(guò)加鎖,來(lái)保證當(dāng)前成員變量_userName
的讀寫(xiě)安全器罐,不至于生成臟數(shù)據(jù)梢为,這便是atomic
背后,編譯器幫我們做的事情轰坊。事實(shí)上铸董,如果深究下去編譯器幫我們加了什么鎖,其實(shí)并非@synchronized(object)
自旋鎖不會(huì)使線程狀態(tài)發(fā)生切換肴沫,一直處于用戶態(tài)粟害,即線程一直都是active;不會(huì)使線程進(jìn)入阻塞狀態(tài)颤芬,減少了不必要的上下文切換悲幅,執(zhí)行速度快
非自旋鎖在獲取不到鎖的時(shí)候會(huì)進(jìn)入阻塞狀態(tài),從而進(jìn)入內(nèi)核態(tài)驻襟,當(dāng)獲取到鎖的時(shí)候需要從內(nèi)核態(tài)恢復(fù)夺艰,需要線程上下文切換,影響鎖的性能
為什么atomic
會(huì)做為默認(rèn)屬性芋哭,我們不難看出沉衣,蘋(píng)果這么設(shè)計(jì)是想告訴我們,很多情況下减牺,效率換安全是值得的
如何使用鎖
下面一段簡(jiǎn)單代碼豌习,考慮一下輸出結(jié)果
- (void)unlockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[string appendString:@"-Locked"];
NSLog(@"%@",string);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[string appendString:@"-JailBreaked"];
NSLog(@"%@",string);
});
}
書(shū)寫(xiě)這樣一段代碼,是想在不同線程中在改變變量后拔疚,使用這個(gè)變量
控制臺(tái)輸出:
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225442] Mike-Locked-JailBreaked
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225441] Mike-Locked-JailBreaked
這顯然不是想要的結(jié)果肥隆,如何保證我們?cè)诓煌€程中使用的變量,都是我們希望的值呢稚失?答案之一栋艳,就是加鎖
<NSLocking>
OC為我們提供了四種遵循<NSLocking>的類,分別是NSLock
/NSCondtionLock
/NSRecursiveLock
/NSCondition
句各,滿足面向?qū)ο缶幊痰男枨?/p>
@protocol NSLocking
- (void)lock;// 阻塞線程吸占,線程休眠
- (void)unlock;
@end
加鎖的基本流程: 【加鎖】->【操作】->【解鎖】
以上提到的4個(gè)類,均可以實(shí)現(xiàn)這個(gè)基礎(chǔ)功能凿宾,下文中不再贅述
- (void)lockedTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// [鎖 lock];
[string appendString:@"-Locked"];
NSLog(@"%@",string);
// [鎖 unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [鎖 lock];
[string appendString:@"-JailBreaked"];
NSLog(@"%@",string);
// [鎖 unlock];
});
}
控制臺(tái)輸出:
DiscoverLock_iOS[90562:11303793] Mike-Locked
DiscoverLock_iOS[90562:11303799] Mike-Locked-JailBreaked
DiscoverLock_iOS[90562:11303793] Mike-JailBreaked
DiscoverLock_iOS[90562:11303799] Mike-JailBreaked-Locked
這里的輸出矾屯,結(jié)果不太一樣,側(cè)面說(shuō)明了DISPATCH_QUEUE_PRIORITY
并不能保證線程的執(zhí)行順序初厚,如果要明確執(zhí)行順序件蚕,屬于線程同步的范疇,本文不展開(kāi)討論,只會(huì)在NSConditionLock部分簡(jiǎn)單示例如何使用該類做到同步
NSLock
-
- (BOOL)tryLock;
:嘗試加鎖排作,如果失敗返回NO牵啦,不會(huì)阻塞線程 -
- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定時(shí)間前嘗試加鎖,如果失敗返回NO妄痪,到時(shí)間前阻塞線程
示例代碼:
- (void)lockTest {
NSMutableString *string = [@"Mike" mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
LOCK(
[string appendString:@"-Locked"];
NSLog(@"%@",string);
sleep(5);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
TRYLOCK(
[string appendString:@"-UnLock"];
NSLog(@"%@",string);
sleep(3);
)
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
TRYLOCKINDURATION(2,
[string appendString:@"-Ending"];
NSLog(@"%@",string);
);
NSLog(@"-=-=-=-=-");
});
}
控制臺(tái)輸出:
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465678] Mike-Locked
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465679] TryLock-NO
2019-11-11 19:54:08.807889+0800 DiscoverLock_iOS[92986:11465679] Mike-Locked-UnLock
2019-11-11 19:54:10.810165+0800 DiscoverLock_iOS[92986:11465677] TryLockBefore-NO
2019-11-11 19:54:10.810523+0800 DiscoverLock_iOS[92986:11465677] Mike-Locked-UnLock-Ending
2019-11-11 19:54:10.810810+0800 DiscoverLock_iOS[92986:11465677] -=-=-=-=-
通過(guò)上面示例代碼輸出可以看到蕾久,- (BOOL)tryLock;
并不會(huì)阻塞線程,在嘗試加鎖失敗時(shí)拌夏,立即返回了NO,但是- (BOOL)lockBeforeDate:(NSDate *)limit;
則在時(shí)間到之前阻塞了線程操作僧著,在等待相應(yīng)時(shí)間后,返回了NO障簿,并執(zhí)行了下一句打印盹愚,很明顯是在等待期間阻塞了線程
上面代碼中用到的幾個(gè)宏定義,建議以后使用鎖時(shí)站故,盡量保持頭腦清醒或者干脆定義一些便利方法皆怕,保證【上鎖】-【解鎖】的成對(duì)出現(xiàn),避免線程阻塞或死鎖的情況
#define LOCK(...) \
[_lock lock]; \
__VA_ARGS__; \
[_lock unlock]; \
#define TRYLOCK(...) \
BOOL locked = [_lock tryLock]; \
NSLog(@"%@",locked?@"TryLock-YES":@"TryLock-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
#define TRYLOCKINDURATION(duration,...) \
BOOL locked = [_lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:duration]]; \
NSLog(@"%@",locked?@"TryLockBefore-YES":@"TryLockBefore-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \
NSConditionLock
-
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
:便利構(gòu)造方法西篓,傳入條件鎖的初始值 -
@property (readonly) NSInteger condition;
:當(dāng)前條件鎖的值 -
- (void)lockWhenCondition:(NSInteger)condition;
:當(dāng)鎖的條件值與傳入值相等時(shí)愈腾,執(zhí)行接下來(lái)的操作,否則阻塞線程 -
- (BOOL)tryLock;
:嘗試加鎖岂津,如果失敗返回NO虱黄,不會(huì)阻塞線程 -
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
:嘗試加鎖,當(dāng)鎖的條件值與傳入值相等吮成,則加鎖成功橱乱,否則失敗返回NO,不會(huì)阻塞線程 -
- (void)unlockWithCondition:(NSInteger)condition;
:解鎖操作粱甫,同時(shí)變更鎖的條件值為傳入值 -
- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定時(shí)間前嘗試加鎖泳叠,如果失敗返回NO,到時(shí)間前阻塞線程 -
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
:指定時(shí)間前嘗試加鎖茶宵,當(dāng)鎖的條件值與傳入值相等危纫,則加鎖成功返回YES,否則失敗返回NO乌庶,到時(shí)間前阻塞線程
NSConditionLock
和NSLock
方法類似种蝶,多了一個(gè)condition
屬性,以及每個(gè)操作都多了一個(gè)關(guān)于condition屬性的方法安拟,- (void)lockWhenCondition:(NSInteger)condition;
只有condition參數(shù)與初始化時(shí)候的condition相等蛤吓,lock才能正確進(jìn)行加鎖操作。而- (void)unlockWithCondition:(NSInteger)condition;
并不是當(dāng)條件值符合條件時(shí)才解鎖糠赦,而是解鎖之后,修改當(dāng)前鎖的條件值
假如不使用condition相關(guān)的方法会傲,NSConditionLock
同NSLock
并無(wú)二致
上文中我們提到了線程同步問(wèn)題锅棕,這里一起看一下下面這段代碼
- (void)conditionLockUnordered {
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-2-"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-3-"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-4-"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionString appendString:@"-5-"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
});
}
控制臺(tái)輸出:
2019-11-11 20:34:16.875479+0800 DiscoverLock_iOS[93895:11551560] >>> 2 -1--2--4--3- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<
2019-11-11 20:34:16.875525+0800 DiscoverLock_iOS[93895:11551562] >>> 3 -1--2--4--3- threadInfo:<NSThread: 0x600003903680>{number = 6, name = (null)}<<<
2019-11-11 20:34:16.875530+0800 DiscoverLock_iOS[93895:11551561] >>> 1 -1--2- threadInfo:<NSThread: 0x600003908bc0>{number = 3, name = (null)}<<<
2019-11-11 20:34:16.875543+0800 DiscoverLock_iOS[93895:11551559] >>> 4 -1--2--4--3- threadInfo:<NSThread: 0x6000039175c0>{number = 5, name = (null)}<<<
2019-11-11 20:34:16.875628+0800 DiscoverLock_iOS[93895:11551560] >>> 5 -1--2--4--3--5- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<
依然是混亂狀態(tài),上文中NSLock
部分已經(jīng)通過(guò)加鎖淌山,控制了讀寫(xiě)的穩(wěn)定性裸燎,那么如果我們想要按照標(biāo)號(hào)依次執(zhí)行,該如何操作泼疑?
熟悉GCD
的小伙伴會(huì)說(shuō)這還不簡(jiǎn)單德绿,dispatch_barrier
解千愁,當(dāng)然這么寫(xiě)沒(méi)問(wèn)題退渗,但是這里多說(shuō)一嘴移稳,dispatch_barrier
只能針對(duì)同一個(gè)并發(fā)隊(duì)列起作用,注意正確初始化的姿勢(shì)dispatch_queue_t thread = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
,而不是干啥都是一句dispatch_get_global_queue(0,0)
,如果使用Global_Queue,這個(gè)barrier就同普通的dispatch_async
沒(méi)什么區(qū)別了
我們要是想在不同線程搞定順序這個(gè)事兒会油,怎么辦呢个粱?這個(gè)時(shí)候NSConditionLock
自帶的條件方法,便能幫你實(shí)現(xiàn)這個(gè)功能翻翩,具體看下面的示例代碼
- (void)conditionLockOrdered {
// NSConditionLock
NSInteger conditionTag = 0;
_conditionLock = [[NSConditionLock alloc] initWithCondition:conditionTag];
NSMutableString *conditionString = [[NSMutableString alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 1 <<<");
[_conditionLock lockWhenCondition:conditionTag];
[conditionString appendString:@"-1-"];
NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 2 <<<");
[_conditionLock lockWhenCondition:1];
[conditionString appendString:@"-2-"];
NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:2];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@">>> handle 3 <<<");
[_conditionLock lockWhenCondition:2];
[conditionString appendString:@"-3-"];
NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:3];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 4 <<<");
[_conditionLock lockWhenCondition:3];
[conditionString appendString:@"-4-"];
NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlockWithCondition:4];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@">>> handle 5 <<<");
[_conditionLock lockWhenCondition:4];
[conditionString appendString:@"-5-"];
NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
[_conditionLock unlock];
NSLog(@"-=-=-=-=-=-=-");
});
NSLog(@"??");
}
控制臺(tái)輸出:
2019-11-11 20:53:58.237847+0800 DiscoverLock_iOS[94374:11586439] ??
2019-11-11 20:53:58.237862+0800 DiscoverLock_iOS[94374:11586488] >>> handle 1 <<<
2019-11-11 20:53:58.237877+0800 DiscoverLock_iOS[94374:11586489] >>> handle 3 <<<
2019-11-11 20:53:58.237868+0800 DiscoverLock_iOS[94374:11586490] >>> handle 2 <<<
2019-11-11 20:53:58.237887+0800 DiscoverLock_iOS[94374:11586491] >>> handle 4 <<<
2019-11-11 20:53:58.237892+0800 DiscoverLock_iOS[94374:11586495] >>> handle 5 <<<
2019-11-11 20:53:58.238111+0800 DiscoverLock_iOS[94374:11586488] >>> 1 -1- threadInfo:<NSThread: 0x6000014c3380>{number = 3, name = (null)}<<<
2019-11-11 20:53:58.238488+0800 DiscoverLock_iOS[94374:11586490] >>> 2 -1--2- threadInfo:<NSThread: 0x6000014dac40>{number = 4, name = (null)}<<<
2019-11-11 20:53:58.238605+0800 DiscoverLock_iOS[94374:11586489] >>> 3 -1--2--3- threadInfo:<NSThread: 0x6000014daf00>{number = 5, name = (null)}<<<
2019-11-11 20:53:58.239269+0800 DiscoverLock_iOS[94374:11586491] >>> 4 -1--2--3--4- threadInfo:<NSThread: 0x6000014c6740>{number = 6, name = (null)}<<<
2019-11-11 20:53:58.239410+0800 DiscoverLock_iOS[94374:11586495] >>> 5 -1--2--3--4--5- threadInfo:<NSThread: 0x6000014c3480>{number = 7, name = (null)}<<<
2019-11-11 20:53:58.239552+0800 DiscoverLock_iOS[94374:11586495] -=-=-=-=-=-=-
可以看到都许,不同的線程,雖然被調(diào)度的時(shí)機(jī)不同嫂冻,但是因?yàn)?code>NSConditionLock的存在胶征,后續(xù)對(duì)數(shù)據(jù)具體的操作,我們預(yù)想的順序得到了保證桨仿。這種用法筆者并認(rèn)為在任務(wù)耗時(shí)較少的情況下沒(méi)有明顯問(wèn)題的睛低,但是假如存在長(zhǎng)時(shí)間的耗時(shí)操作,還是建議使用dispatch_barrier
蹬敲,因?yàn)檫@樣不會(huì)占用過(guò)多資源
NSRecursiveLock
-
- (BOOL)tryLock;
:嘗試加鎖暇昂,如果失敗返回NO莺戒,不會(huì)阻塞線程 -
- (BOOL)lockBeforeDate:(NSDate *)limit;
:指定時(shí)間前嘗試加鎖伴嗡,如果失敗返回NO,到時(shí)間前阻塞線程
Api同NSLock
完全一樣从铲,區(qū)別在于NSRecursiveLock(遞歸鎖)
可以在同一線程中重復(fù)加鎖而不死鎖瘪校,它會(huì)記錄【上鎖】和【解鎖】的次數(shù),當(dāng)這兩個(gè)值平衡時(shí)名段,才會(huì)釋放鎖阱扬,其他線程才可以上鎖成功
先看下一段代碼,會(huì)存在什么問(wèn)題:
@property (nonatomic, assign) NSInteger recursiveNum;// 5
- (void)test_unrecursiveLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursiveTest];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.recursiveNum = 7;
NSLog(@">>> changed %ld <<<",self.recursiveNum);
});
}
- (void)recursiveTest {
if (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum);
[self recursiveTest];
}
}
控制臺(tái)輸出:
2019-11-11 21:27:13.451703+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.451709+0800 DiscoverLock_iOS[95105:11645277] >>> changed 7 <<<
2019-11-11 21:27:13.451812+0800 DiscoverLock_iOS[95105:11645279] >>> 6 <<<
2019-11-11 21:27:13.451883+0800 DiscoverLock_iOS[95105:11645279] >>> 5 <<<
2019-11-11 21:27:13.451940+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.452004+0800 DiscoverLock_iOS[95105:11645279] >>> 3 <<<
2019-11-11 21:27:13.452068+0800 DiscoverLock_iOS[95105:11645279] >>> 2 <<<
2019-11-11 21:27:13.452130+0800 DiscoverLock_iOS[95105:11645279] >>> 1 <<<
2019-11-11 21:27:13.452241+0800 DiscoverLock_iOS[95105:11645279] >>> 0 <<<
同時(shí)存在兩個(gè)線程伸辟,對(duì)已知的recursiveNum的值進(jìn)行寫(xiě)操作麻惶,其中一個(gè)線程使用遞歸調(diào)用,對(duì)該值進(jìn)行了操作信夫,但是同時(shí)另一個(gè)線程改變了這個(gè)值窃蹋,在不加鎖的情況下卡啰,這種操作問(wèn)題很多,如果遞歸中含有重要的邏輯處理警没,競(jìng)態(tài)可能導(dǎo)致整個(gè)邏輯執(zhí)行完的結(jié)果大概率是錯(cuò)誤的匈辱。
如何規(guī)避這種競(jìng)態(tài)導(dǎo)致的不必要的錯(cuò)誤,首先我們想到的是加鎖杀迹,但是如果遞歸加鎖的話亡脸,線程會(huì)重復(fù)加鎖,導(dǎo)致死鎖树酪。所以這時(shí)候必須使用遞歸鎖來(lái)解決這個(gè)問(wèn)題
- (void)test_unrecursiveLock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursiveTest];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[_recursiveLock lock];// 遞歸鎖
self.recursiveNum = 7;
NSLog(@">>> changed %ld <<<",self.recursiveNum);
[_recursiveLock unlock];// 解鎖
});
}
- (void)recursiveTest {
[_recursiveLock lock];// 遞歸鎖
if (self.recursiveNum > 0) {
self.recursiveNum -= 1;
NSLog(@">>> %ld <<<",self.recursiveNum);
[self recursiveTest];
}
[_recursiveLock unlock];// 解鎖
}
控制臺(tái)輸出:
2019-11-11 21:34:44.422337+0800 DiscoverLock_iOS[95341:11655990] >>> 4 <<<
2019-11-11 21:34:44.422442+0800 DiscoverLock_iOS[95341:11655990] >>> 3 <<<
2019-11-11 21:34:44.422511+0800 DiscoverLock_iOS[95341:11655990] >>> 2 <<<
2019-11-11 21:34:44.422583+0800 DiscoverLock_iOS[95341:11655990] >>> 1 <<<
2019-11-11 21:34:44.422645+0800 DiscoverLock_iOS[95341:11655990] >>> 0 <<<
2019-11-11 21:34:44.422747+0800 DiscoverLock_iOS[95341:11655992] >>> changed 7 <<<
------
2019-11-11 21:37:11.238448+0800 DiscoverLock_iOS[95396:11662426] >>> changed 7 <<<
2019-11-11 21:37:11.238635+0800 DiscoverLock_iOS[95396:11662423] >>> 6 <<<
2019-11-11 21:37:11.238793+0800 DiscoverLock_iOS[95396:11662423] >>> 5 <<<
2019-11-11 21:37:11.238930+0800 DiscoverLock_iOS[95396:11662423] >>> 4 <<<
2019-11-11 21:37:11.239093+0800 DiscoverLock_iOS[95396:11662423] >>> 3 <<<
2019-11-11 21:37:11.239293+0800 DiscoverLock_iOS[95396:11662423] >>> 2 <<<
2019-11-11 21:37:11.239844+0800 DiscoverLock_iOS[95396:11662423] >>> 1 <<<
2019-11-11 21:37:11.239976+0800 DiscoverLock_iOS[95396:11662423] >>> 0 <<<
雖然存在兩種輸出結(jié)果浅碾,但是我們的遞歸操作的邏輯,可以完全不受干擾续语,如果需要控制順序及穗,(敲黑板)要怎么做呢?
NSCondition
-
- (void)wait;
:當(dāng)前線程立即進(jìn)入休眠狀態(tài) -
- (BOOL)waitUntilDate:(NSDate *)limit;
:當(dāng)前線程立即進(jìn)入休眠狀態(tài)绵载,limit時(shí)間后喚醒 -
- (void)signal;
:喚醒wait后進(jìn)入休眠的單條線程 -
- (void)broadcast;
:喚醒wait后進(jìn)入休眠的所有線程埂陆,調(diào)度
有些情況需要協(xié)調(diào)線程之間的執(zhí)行。例如娃豹,一個(gè)線程可能需要等待其他線程返回結(jié)果焚虱,這個(gè)時(shí)候NSCondition
可能是個(gè)好選擇
為了能體現(xiàn)NSCondition的作用,我們舉一個(gè)可能并不是很恰當(dāng)?shù)?strong>生產(chǎn)者-消費(fèi)者的例子:
我們現(xiàn)在有一條柔性生產(chǎn)線懂版,限定每個(gè)批次只能生產(chǎn)3件商品鹃栽,耗時(shí)6s,同時(shí)開(kāi)放網(wǎng)絡(luò)購(gòu)買(mǎi)平臺(tái)讓大家搶購(gòu)拼團(tuán)躯畴,訂單式銷售民鼓,三人成團(tuán),現(xiàn)在有三位天選之子 Tom/Mike/Lily 從全球千萬(wàn)人中脫穎而出蓬抄,成功成團(tuán)丰嘉。為了增強(qiáng)可玩性,活動(dòng)是從開(kāi)啟的一刻起嚷缭,同時(shí)開(kāi)始生產(chǎn)和搶購(gòu)饮亏,3件庫(kù)存銷售完成后,再次進(jìn)行同時(shí)進(jìn)行生產(chǎn)和搶購(gòu)活動(dòng)
代碼示例如下:
@interface Producer : NSObject
@property (nonatomic, assign) BOOL shouldProduce;
@property (nonatomic, strong) NSString *itemName;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector;
- (void)produce;
@end
@implementation Producer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldProduce = NO;
self.itemName = nil;
}
return self;
}
-(void)produce{
self.shouldProduce = YES;
while (self.shouldProduce) {
NSLog(@"準(zhǔn)備生產(chǎn)");
[self.condition lock];
NSLog(@"- p lock -");
if (self.collector.count > 0 ) {
NSLog(@"- p - wait");
[self.condition wait];
}
NSLog(@"開(kāi)始生產(chǎn)");
[self.collector addObject:@"商品1"];
[self.collector addObject:@"商品2"];
[self.collector addObject:@"商品3"];
NSLog(@"生產(chǎn):商品1/商品2/商品3");
sleep(6);
NSLog(@"生產(chǎn)結(jié)束");
[self.condition broadcast];
NSLog(@"- p signal -");
[self.condition unlock];
NSLog(@"- p unlock -");
}
NSLog(@"-結(jié)束生產(chǎn)-");
}
@end
@interface Consumer : NSObject
@property (nonatomic, assign) BOOL shouldConsumer;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
@property (nonatomic, copy) NSString *itemName;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name;
- (void)consumer;
@end
@implementation Consumer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name{
self = [super init];
if (self) {
self.condition = condition;
self.collector = collector;
self.shouldConsumer = NO;
self.itemName = name;
}
return self;
}
-(void)consumer{
self.shouldConsumer = YES;
while (self.shouldConsumer) {
NSLog(@"%@-準(zhǔn)備購(gòu)買(mǎi)",self.itemName);
[self.condition lock];
NSLog(@"- c:%@ lock -",self.itemName);
if (self.collector.count == 0 ) {
NSLog(@"- c:%@ wait -",self.itemName);
[self.condition wait];
}
NSString *item = [self.collector objectAtIndex:0];
NSLog(@"%@-買(mǎi)入:%@",self.itemName,item);
[self.collector removeObjectAtIndex:0];
sleep(2);
[self.condition signal];
NSLog(@"- c:%@ signal -",self.itemName);
[self.condition unlock];
NSLog(@"- c:%@ unlock -",self.itemName);
}
NSLog(@"-%@結(jié)束購(gòu)買(mǎi)-",self.itemName);
}
@end
// 調(diào)用
{
NSMutableArray *pipeline = [NSMutableArray array];
NSCondition *condition = [NSCondition new];
Producer *p = [[Producer alloc] initWithConditon:condition collector:pipeline];
Consumer *c = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Tom"];
Consumer *c1 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Mike"];
Consumer *c2 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Lily"];
[[[NSThread alloc] initWithTarget:c selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c1 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:c2 selector:@selector(consumer) object:c] start];
[[[NSThread alloc] initWithTarget:p selector:@selector(produce) object:p] start];
sleep(15);
NSLog(@"<----------------->");
p.shouldProduce = NO;
c.shouldConsumer = NO;
c1.shouldConsumer = NO;
c2.shouldConsumer = NO;
}
部分控制臺(tái)輸出:
2019-11-12 17:04:03.662926+0800 DiscoverLock_iOS[7110:12246052] Mike-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.662916+0800 DiscoverLock_iOS[7110:12246051] Tom-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.662990+0800 DiscoverLock_iOS[7110:12246053] Lily-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.663005+0800 DiscoverLock_iOS[7110:12246054] 準(zhǔn)備生產(chǎn)
2019-11-12 17:04:03.663083+0800 DiscoverLock_iOS[7110:12246053] - c:Lily lock -
2019-11-12 17:04:03.663144+0800 DiscoverLock_iOS[7110:12246053] - c:Lily wait -
2019-11-12 17:04:03.663254+0800 DiscoverLock_iOS[7110:12246052] - c:Mike lock -
2019-11-12 17:04:03.663439+0800 DiscoverLock_iOS[7110:12246052] - c:Mike wait -
2019-11-12 17:04:03.663805+0800 DiscoverLock_iOS[7110:12246051] - c:Tom lock -
2019-11-12 17:04:03.663903+0800 DiscoverLock_iOS[7110:12246051] - c:Tom wait -
2019-11-12 17:04:03.664126+0800 DiscoverLock_iOS[7110:12246054] - p lock -
2019-11-12 17:04:03.664297+0800 DiscoverLock_iOS[7110:12246054] 開(kāi)始生產(chǎn)
2019-11-12 17:04:03.664433+0800 DiscoverLock_iOS[7110:12246054] 生產(chǎn):商品1/商品2/商品3
2019-11-12 17:04:09.669735+0800 DiscoverLock_iOS[7110:12246054] 生產(chǎn)結(jié)束
基于多線程并發(fā)的工作原理阅爽,通過(guò)上面的部分打印結(jié)果路幸,也很容易得到這個(gè)結(jié)論。由于不符合購(gòu)買(mǎi)條件付翁,Lily/Mike/Tom都只能選擇wait
简肴,這個(gè)時(shí)候,生產(chǎn)者獲取到鎖并執(zhí)行生產(chǎn)代碼百侧,在生產(chǎn)完成后砰识,broadcast
或者signal
告訴其他線程杂伟,可以喚醒線程并繼續(xù)執(zhí)行消費(fèi)者相關(guān)代碼。
NSCondition
相較于NSConditionLock
的不同點(diǎn)在于他依賴的是外部值仍翰,能夠滿足更多復(fù)雜需求場(chǎng)景赫粥。
假如將上述代碼中生產(chǎn)者的broadcast
替換成signal
后發(fā)現(xiàn),在當(dāng)前這種特定場(chǎng)景下予借,這兩個(gè)方法的作用似乎并沒(méi)有什么區(qū)別越平。而且感興趣的同學(xué),可以使用上述代碼多運(yùn)行幾次灵迫,看看是否能夠得出同筆者一樣的猜測(cè):
- NSCondition會(huì)自身通過(guò)隊(duì)列管理協(xié)同任務(wù)的調(diào)度
- wait的任務(wù)依次入等待隊(duì)列
- 未wait的任務(wù)根據(jù)獲得鎖的順序依次入執(zhí)行隊(duì)列
- wait任務(wù)的等待隊(duì)列會(huì)在執(zhí)行隊(duì)列執(zhí)行完后依次執(zhí)行并入執(zhí)行隊(duì)列
- 第一次調(diào)度順序確定后秦叛,后續(xù)任務(wù)的執(zhí)行,按照?qǐng)?zhí)行隊(duì)列緩存依次出列執(zhí)行
這里僅做猜想瀑粥,具體實(shí)現(xiàn)可能并非如此挣跋,待大佬指點(diǎn)迷津或有機(jī)會(huì)鶸筆者自行研究
OSSpinLock
看了NSCondition
這么個(gè)復(fù)雜的東西,我們看點(diǎn)輕松的狞换,OSSpinLock
是蘋(píng)果在iOS10之前提供的自旋鎖方案避咆,但是存在優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題,被蘋(píng)果廢棄修噪,以前源碼中使用OSSpinLock
的地方查库,都被蘋(píng)果替換成了pthread_mutex
os_unfair_lock
os_unfair_lock
是iOS10以后新增的低級(jí)別加鎖方案,本質(zhì)是互斥鎖黄琼,這里需要注意樊销,目前很多文章認(rèn)為他是作為替代OSSpinLock
的方案就是自旋鎖是有問(wèn)題的
-
void os_unfair_lock_lock(os_unfair_lock_t lock);
:加鎖 -
bool os_unfair_lock_trylock(os_unfair_lock_t lock);
:嘗試加鎖,成功返回true脏款,失敗返回false -
void os_unfair_lock_unlock(os_unfair_lock_t lock);
:解鎖 -
void os_unfair_lock_assert_owner(os_unfair_lock_t lock);
:如果當(dāng)前線程未持有指定的鎖围苫,則觸發(fā)斷言 -
void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);
:如果當(dāng)前線程持有指定的鎖,則觸發(fā)斷言
各方法同常見(jiàn)的鎖沒(méi)太大差別撤师,可以看下方法注釋剂府,只是需要注意一下初始化方式
{
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
}
@synchronize(object)
@synchronized(object)
指令使用傳入的對(duì)象作為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí)丈氓,才滿足互斥
@synchronized(object)
指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對(duì)象周循,便可以實(shí)現(xiàn)鎖的機(jī)制,而且不用擔(dān)心忘記解鎖
使用方法極其常見(jiàn)万俗,不做示例了
dispatch_semaphore
-
dispatch_semaphore_t dispatch_semaphore_create(long value);
:創(chuàng)建信號(hào)量,傳入初始值 -
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
:當(dāng)信號(hào)<=0時(shí)饮怯,根據(jù)傳入的時(shí)間阻塞線程闰歪;如果信號(hào)>0則不阻塞線程,并對(duì)信號(hào)-1處理 -
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
:對(duì)信號(hào)+1處理
GCD
為我們提供的信號(hào)量也是常用的加鎖方式蓖墅,常見(jiàn)用法是初始化信號(hào)值為1
{
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER);
// 操作
dispatch_semaphare_signal(lock);
}
常規(guī)操作大家都知道库倘,有常規(guī)操作临扮,那么一定也有非常規(guī)操作,可以看一下AFNetwork
給我們的示范
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
在AFURLSessionManager
中教翩,初始化使用dispatch_semaphore_create(0)
杆勇,在return tasks;
前調(diào)用dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
阻塞線程,待block將目標(biāo)值賦值后饱亿,執(zhí)行dispatch_semaphore_signal(semaphore);
,此時(shí)tasks已經(jīng)有值蚜退,線程被喚醒后正常返回。很秀
pthread_mutex
C語(yǔ)言下的互斥鎖方案彪笼,是<NSLocking>協(xié)議下四個(gè)類的底層
鎖常用函數(shù):
-
pthread_mutex_init
:動(dòng)態(tài)初始化互斥量 -
PTHREAD_MUTEX_INITIALIZER
:靜態(tài)創(chuàng)建互斥量 -
pthread_mutex_lock
:給一個(gè)互斥量加鎖 -
pthread_mutex_trylock
:加鎖钻注,如果失敗不阻塞 -
pthread_mutex_unlock
:解鎖 -
pthread_mutex_destroy
:銷毀鎖
參數(shù)配置函數(shù):
-
pthread_mutexattr_init
:初始化參數(shù) -
pthread_mutexattr_settype
:設(shè)置類型 -
pthread_mutexattr_setpshared
:設(shè)置作用域 -
pthread_mutexattr_destroy
:銷毀參數(shù)
條件常見(jiàn)函數(shù):
-
pthread_cond_init
:動(dòng)態(tài)初始化條件量 -
PTHREAD_COND_INITIALIZER
:靜態(tài)創(chuàng)建條件量 -
pthread_cond_wait
:傳入條件量及鎖 -
pthread_cond_signal
:喚醒單條線程并加鎖 -
pthread_cond_broadcast
:廣播喚醒所有線程 -
pthread_cond_destroy
:銷毀條件
以上函數(shù)都是有返回值的,需要注意的是配猫,若成功則返回0幅恋,否則返回錯(cuò)誤編號(hào),不是我們習(xí)慣中的成功YES失敗NO
鎖類型:
-
PTHREAD_MUTEX_NORMAL
:缺省值泵肄,這種類型的互斥鎖不會(huì)自動(dòng)檢測(cè)死鎖捆交。如果一個(gè)線程試圖對(duì)一個(gè)互斥鎖重復(fù)鎖定,將會(huì)引起這個(gè)線程的死鎖腐巢。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖會(huì)引發(fā)不可預(yù)料的結(jié)果零渐。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也會(huì)引發(fā)不可預(yù)料的結(jié)果 -
PTHREAD_MUTEX_ERRORCHECK
:這種類型的互斥鎖會(huì)自動(dòng)檢測(cè)死鎖。如果一個(gè)線程試圖對(duì)一個(gè)互斥鎖重復(fù)鎖定系忙,將會(huì)返回一個(gè)錯(cuò)誤代碼诵盼。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖將會(huì)返回一個(gè)錯(cuò)誤代碼。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也將會(huì)返回一個(gè)錯(cuò)誤代碼 -
PTHREAD_MUTEX_RECURSIVE
:如果一個(gè)線程對(duì)這種類型的互斥鎖重復(fù)上鎖银还,不會(huì)引起死鎖风宁,一個(gè)線程對(duì)這類互斥鎖的多次重復(fù)上鎖必須由這個(gè)線程來(lái)重復(fù)相同數(shù)量的解鎖,這樣才能解開(kāi)這個(gè)互斥鎖蛹疯,別的線程才能得到這個(gè)互斥鎖戒财。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖將會(huì)返回一個(gè)錯(cuò)誤代碼。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也將會(huì)返回一個(gè)錯(cuò)誤代碼捺弦。這種類型的互斥鎖只能是進(jìn)程私有的(作用域?qū)傩訮THREAD_PROCESS_PRIVATE) -
PTHREAD_MUTEX_DEFAULT
:就是NORMAL類型
鎖作用域:
-
PTHREAD_PROCESS_PRIVATE
:缺省值饮寞,作用域?yàn)檫M(jìn)程內(nèi) -
PTHREAD_PROCESS_SHARED
:作用域?yàn)檫M(jìn)程間
使用示例:
static pthread_mutex_t c_lock;
- (void)testPthread_mutex {
pthread_mutexattr_t c_lockAttr;
pthread_mutexattr_init(&c_lockAttr);
pthread_mutexattr_settype(&c_lockAttr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutexattr_setpshared(&c_lockAttr, PTHREAD_PROCESS_PRIVATE);
pthread_mutex_init(&c_lock, &c_lockAttr);
pthread_mutexattr_destroy(&c_lockAttr);
pthread_t thread1;
pthread_create(&thread1, NULL, _thread1, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, _thread2, NULL);
}
void *_thread1() {
pthread_mutex_lock(&c_lock);
printf("thread 1\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
void *_thread2() {
pthread_mutex_lock(&c_lock);
printf("thread 2 busy\n");
sleep(3);
printf("thread 2\n");
pthread_mutex_unlock(&c_lock);
return 0;
}
使用鎖的注意點(diǎn)
互斥量需要時(shí)間來(lái)加鎖和解鎖。鎖住較少互斥量的程序通常運(yùn)行得更快列吼。所以幽崩,互斥量應(yīng)該盡量少,夠用即可寞钥,每個(gè)互斥量保護(hù)的區(qū)域應(yīng)則盡量大慌申。
互斥量的本質(zhì)是串行執(zhí)行。如果很多線程需要領(lǐng)繁地加鎖同一個(gè)互斥量理郑,
則線程的大部分時(shí)間就會(huì)在等待蹄溉,這對(duì)性能是有害的咨油。如果互斥量保護(hù)的數(shù)據(jù)(或代碼)包含彼此無(wú)關(guān)的片段,則可以特大的互斥量分解為幾個(gè)小的互斥量來(lái)提高性能柒爵。這樣役电,任意時(shí)刻需要小互斥量的線程減少,線程等待時(shí)間就會(huì)減少棉胀。所以法瑟,互斥量應(yīng)該足夠多(到有意義的地步),每個(gè)互斥量保護(hù)的區(qū)域則應(yīng)盡量的少膏蚓。
參考文檔
Posix互斥量pthread_mutex_t
iOS 常見(jiàn)知識(shí)點(diǎn)(三):Lock
不再安全的 OSSpinLock
How does @synchronized lock/unlock in Objective-C?
[爆棧熱門(mén) iOS 問(wèn)題] atomic 和 nonatomic 有什么區(qū)別瓢谢?
《高性能iOS應(yīng)用開(kāi)發(fā)中文版》