上一節(jié)對(duì)鎖家族
的@synchronized
進(jìn)行源碼解析
固耘,本節(jié)將對(duì)鎖家族
的其他2位NSLock
和NSCondition
進(jìn)行源碼分析题篷。
-
鎖家族
全家福(耗時(shí)圖
):
image.png
- NSLock應(yīng)用與源碼
- NSLock、NSRecursiveLock厅目、@synchronized三者的區(qū)別
- NSCondition
- NSConditionLock
1. NSLock
- 測(cè)試代碼
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *testArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self demo];
}
- (void)demo {
NSLog(@"123");
self.testArray = [NSMutableArray array];
NSLock * lock = [[NSLock alloc] init]; // 創(chuàng)建
for (int i = 0; i < 20000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock]; // 加鎖
self.testArray = [NSMutableArray array];
[lock unlock]; // 解鎖
});
}
}
@end
- 進(jìn)入
NSLock
番枚,可以看到它遵循NSLocking協(xié)議
:
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> { ... }
...
@end
@interface NSConditionLock : NSObject <NSLocking> { ... }
...
@end
@interface NSRecursiveLock : NSObject <NSLocking> { ... }
...
@end
@interface NSCondition : NSObject <NSLocking> { ... }
...
@end
NSLocking協(xié)議
包含lock
和unlock
兩個(gè)方法法严。NSLock
、NSConditionLock
葫笼、NSRecursiveLock
深啤、NSCondition
都遵循NSLocking協(xié)議
- 現(xiàn)在,我們開(kāi)始尋找
lock
源碼的出處:
方法一: 在代碼
[lock lock]加鎖
處中路星,加入斷點(diǎn)
溯街,打開(kāi)debug匯編模式
,一步步執(zhí)行洋丐,查詢?cè)创a
的出處: 很遺憾呈昔,發(fā)現(xiàn)找不到方法二:
直接斷點(diǎn)
進(jìn)不去,那我們運(yùn)行到斷點(diǎn)
處后友绝,加入lock符號(hào)斷點(diǎn)
堤尾,再運(yùn)行代碼,發(fā)現(xiàn)找到了迁客,在Foundation庫(kù)
中執(zhí)行的:
image.png
可是
Foudation
庫(kù)是未開(kāi)源庫(kù)
郭宝,我們無(wú)法獲取
到源碼
坛梁。但是swift
是開(kāi)源語(yǔ)言
巧勤。我們可以參考swift Foudation庫(kù)
。-
打開(kāi)
swift Foundation
庫(kù)桃犬,搜索class NSLock
:
image.png
我們發(fā)現(xiàn):
- 1.
init
中初始化了pthread_mutex
lock
和unlock
實(shí)際都是調(diào)用
了pthread_mutex
相對(duì)于的lock
和unlock
函數(shù)順便探究
NSRecursiveLock
卜范、NSCondition
和NSConditionLock
:
- 發(fā)現(xiàn)
NSRecursiveLock
衔统、NSCodition
也是基于pthread_mutex
封裝的,但:NSRecursiveLock
比NSLock
多了一層遞歸邏輯
NSCodition
比NSLock
多了一層pthread_con_init
條件鎖海雪。NSConditionLock
是在NSCondition
的基礎(chǔ)上進(jìn)行的再次封裝缰冤。NSRecursiveLockNSCondition
NSConditionLock
結(jié)論:
鎖
必須調(diào)用init
方法(new
內(nèi)部也調(diào)用了init
方法),因?yàn)?code>init會(huì)完成底層pthread_mutex相關(guān)鎖
的初始化
- 所有
遵循NSLocking
協(xié)議的鎖
類喳魏,底層都是基于pthread_mutex
鎖來(lái)實(shí)現(xiàn)的,只是封裝
的深度不同
怀薛。
NSLock
性能接近pthread_mutex
刺彩,而pthread_mutex(recursive)
、NSRecursiveLock
枝恋、NSCondition
创倔、NSConditionLock
的耗時(shí)
一個(gè)比一個(gè)高
,就是由對(duì)pthread_mutex
的封裝深度
決定的焚碌。
2. NSLock畦攘、NSRecursiveLock、@synchronized三者的區(qū)別
我們通過(guò)一個(gè)案例
來(lái)進(jìn)行分析
和對(duì)比
:
案例:
循環(huán)生成
多個(gè)全局隊(duì)列的異步線程
十电,每個(gè)線程內(nèi)聲明block(testMethod)
->實(shí)現(xiàn)block
->調(diào)用block
->嵌套調(diào)用block(遞歸調(diào)用)
要求: 分別使用
NSLock
知押、NSRecursiveLock
叹螟、@synchronized
實(shí)現(xiàn)讀寫安全
- (void)demo{
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);// 1. 聲明
testMethod = ^(int value){ // 2. 實(shí)現(xiàn)Block塊
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1); // 4. 嵌套調(diào)用block
}
};
testMethod(10); // 3.調(diào)用block
});
}
}
2.1 使用NSLock
:
必須在Block實(shí)現(xiàn)前
加鎖,在調(diào)用后解鎖
:
相關(guān)實(shí)踐:
調(diào)用前
加鎖: 死鎖
image.png
- 僅在
第一次
進(jìn)入block
時(shí)打印了一次
台盯,后面就死鎖
了罢绽。
(一直lock
加鎖,而沒(méi)有unlock
解鎖導(dǎo)致的)2.
調(diào)用后
加鎖: 無(wú)效鎖
image.png
- 打印結(jié)果
完全無(wú)序
静盅,鎖
的作用完全消失
(想想都知道良价,block
都執(zhí)行完
了,你再上鎖
蒿叠,有啥用
明垢,鎖
了一堆寂寞
?? )
- 所以如果使用
NSLock鎖
,必須在聲明前加鎖
和調(diào)用后解鎖
市咽,才能解決數(shù)據(jù)
的讀寫安全
問(wèn)題痊银。
?? NSLock
鎖,只鎖了當(dāng)前線程
魂务,當(dāng)我們使用異步多線程
操作時(shí)曼验,可能出現(xiàn)線程
間相互等待
,死鎖
的情況
2.2 使用NSRecursiveLock
-
在
聲明前加鎖
和調(diào)用后解鎖
是正確的粘姜。
image.png -
但由于它具備
遞歸特性
鬓照,我們?cè)?code>block內(nèi)部的遞歸前
加鎖
,當(dāng)前線程
也打印正常
孤紧,但是其他
線程堵塞
豺裆。
image.png -
當(dāng)我們?nèi)サ?code>for循環(huán),僅保持
一個(gè)異步線程
号显,在block內(nèi)部
的遞歸前后
分別加鎖
和解鎖
臭猜,打印正常:
image.png
這是因?yàn)?code>NSRecursiveLock的遞歸特性
。內(nèi)部任務(wù)是遞歸持有
的押蚤,所以不會(huì)死鎖
蔑歌。
2.3 @synchronized
-
@synchronized
最簡(jiǎn)單,直接將block
內(nèi)部代碼包裹
起來(lái)揽碘,就可以實(shí)現(xiàn)數(shù)據(jù)讀寫安全
了
image.png 關(guān)于
@synchronized
的內(nèi)部結(jié)構(gòu)次屠,我們上一節(jié)專門分析了。@synchronized
能對(duì)記錄被鎖對(duì)象
的所有線程
雳刺,每個(gè)線程
內(nèi)部都是遞歸
持有任務(wù)
的劫灶。所以在異步多線程
中,它既不用擔(dān)心遞歸
造成的鎖釋放
問(wèn)題掖桦,也不需要關(guān)心線程間
的通信
問(wèn)題本昏。
NSLock、NSRecursiveLock枪汪、@synchronized三者的區(qū)別
NSLock
:
- 需要
手動(dòng)創(chuàng)建
和釋放
涌穆,需要在準(zhǔn)確的時(shí)機(jī)
進(jìn)行相應(yīng)操作
怔昨。- 僅鎖住
當(dāng)前線程
的當(dāng)前任務(wù)
,無(wú)法
自動(dòng)實(shí)現(xiàn)線程間
的通訊
和遞歸
問(wèn)題蒲犬。
(上述NSLock
代碼實(shí)際上沒(méi)解決遞歸問(wèn)題
朱监,只是野蠻的
在代碼最外層
上了一把大鎖
,無(wú)視
遞歸內(nèi)部層級(jí)
)
NSRecursiveLock
:
- 需要
手動(dòng)創(chuàng)建
和釋放
原叮,需要在準(zhǔn)確的時(shí)機(jī)
進(jìn)行相應(yīng)操作
赫编。- 僅鎖住
當(dāng)前線程
的所有任務(wù)
,無(wú)法
自動(dòng)實(shí)現(xiàn)線程間
的通訊
奋隶,但可以解決遞歸
問(wèn)題擂送。
(與NSLock
不同,NSRecursiveLock
是在遞歸時(shí)
唯欣,每層加鎖
和解鎖
嘹吨。對(duì)鎖的控制
更為精確
)
@synchronized
:
- 只需將
需要鎖
的代碼
都放在作用域內(nèi)
,確定被鎖對(duì)象
(被鎖對(duì)象決定了鎖的生命周期)境氢,@synchronized
就可以做到自動(dòng)創(chuàng)建
和釋放
蟀拷。
鎖
住被鎖對(duì)象
的所有線程
的所有任務(wù)
,可
自動(dòng)實(shí)現(xiàn)線程間
的通訊
萍聊,可以解決遞歸問(wèn)題
问芬。
(內(nèi)部邏輯為:被鎖對(duì)象
可持有多個(gè)線程
,每個(gè)線程
可遞歸
持有多個(gè)任務(wù)
)
所以我們日常使用
時(shí)寿桨,盡管@synchronized耗時(shí)較大
此衅,但是它使用
非常簡(jiǎn)單
,根本不需要處理
各種異常情況
亭螟,也不需要
手動(dòng)釋放
挡鞍。便捷性
和安全性
都非常好
。
3. NSCondition
NSCondition
的對(duì)象實(shí)際上是作為一個(gè)鎖
和一個(gè)線程檢查器
:
-
鎖: 當(dāng)檢查
條件成立
時(shí)预烙,保護(hù)數(shù)據(jù)源
-
線程檢查器:
根據(jù)條件
判斷是否
繼續(xù)運(yùn)行線程
(線程是否阻塞)
方法:
[condition lock]:
加鎖
(一般用于多線程
同時(shí)訪問(wèn)
墨微、修改
同一個(gè)數(shù)據(jù)源
時(shí),保證同一時(shí)間
內(nèi)數(shù)據(jù)源只能被訪問(wèn)
扁掸、修改一次
欢嘿,其他線程
的命令需要在lock外等待
,只有unlock后
也糊,才可訪問(wèn)
)[condition unlock]:
解鎖(與lock配對(duì)
使用)[condition wait]:
讓當(dāng)前線程
處于等待
狀態(tài)[condition signal]:
CPU發(fā)信號(hào)
告訴所有線程
不用再等待,可以繼續(xù)執(zhí)行
羡宙。
- 測(cè)試案例:
有2個(gè)生產(chǎn)者
和2個(gè)消費(fèi)者
狸剃,各自
生產(chǎn)和消費(fèi)各50次
。當(dāng)消費(fèi)者購(gòu)買
時(shí)狗热,沒(méi)貨
就排隊(duì)等待
钞馁,有貨
就賣貨
,一次
只能一個(gè)人買
虑省, 每當(dāng)生產(chǎn)者生產(chǎn)
出一個(gè)貨物
時(shí),都會(huì)廣播
告訴所有等待
的消費(fèi)者
僧凰,進(jìn)行繼續(xù)購(gòu)買
探颈。
這樣保障
了貨品
的數(shù)據(jù)安全
(有貨才能賣,一次賣一個(gè)训措,沒(méi)貨就等待)
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
@property (nonatomic, strong) NSCondition *testCondition;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 0;
[self demo];
}
- (void)demo{
_testCondition = [[NSCondition alloc] init];
//創(chuàng)建生產(chǎn)-消費(fèi)者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; // 生產(chǎn)者
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; // 消費(fèi)者
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; // 消費(fèi)者
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; // 生產(chǎn)者
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多線程影響
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal]; // 發(fā)送信號(hào)
[_testCondition unlock];
}
- (void)consumer{
[_testCondition lock]; // 操作的多線程影響
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait]; // 線程等待
}
//注意消費(fèi)行為伪节,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
@end
-
打印結(jié)果:(生產(chǎn)消費(fèi)數(shù)據(jù)是安全的)
image.png
但是NSCondition
使用非常麻煩
,需要在合適的地方
手動(dòng)加鎖
绩鸣、等待
怀大、發(fā)送信號(hào)
、釋放
于是基于NSCondition
呀闻,出現(xiàn)了NSConditionLock
鎖
4. NSConditionLock
NSConditionLock
是一把鎖
化借,一旦一個(gè)線程
獲得鎖
,其他線程
一定等待
捡多。
方法:
[xxx lock]:
加鎖
- 如果
沒(méi)有
其他線程
獲得鎖
(不需要判斷內(nèi)部的condition)蓖康,那他能執(zhí)行
后續(xù)代碼
,同時(shí)設(shè)置當(dāng)前線程
獲得鎖
垒手。- 如果已經(jīng)
有
其他線程
獲得鎖
(可能是條件鎖蒜焊,或者無(wú)條件鎖),則等待
淫奔,直到
其他線程解鎖
[xxx lockWhenCondition: A條件]:
- 在
[xxx lock]
的基礎(chǔ)
上山涡,沒(méi)有
其他線程
獲得鎖
,且內(nèi)部
的condition
條件滿足A條件
時(shí)唆迁,才
會(huì)執(zhí)行
后續(xù)代碼
并讓當(dāng)前線程
獲得鎖
鸭丛。否則
依舊是等待
[xxx unlockWithCondition: A條件]:
釋放
鎖
- 把
內(nèi)部的condition
設(shè)置為A條件
,并broadcast
廣播告訴所有等待的線程
唐责。return = [xxx lockWhenCondition: A條件 beforeDate: A時(shí)間]:
沒(méi)有
其他線程
獲得鎖
鳞溉,且滿足A條件
,且在A時(shí)間
之前鼠哥,可以執(zhí)行
后續(xù)代碼
并讓當(dāng)前線程
獲得鎖
熟菲。- 返回值為
NO
,表示沒(méi)有改變
鎖的狀態(tài)
condition
是整數(shù)
朴恳,內(nèi)部通過(guò)整數(shù)比較條件
- 通過(guò)下面案例分析:
- (void)demo{
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; // conditoion = 1 內(nèi)部 Condition 匹配
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0]; // 解鎖并把conditoion設(shè)置為0
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2]; // conditoion = 2 內(nèi)部 Condition 匹配
sleep(0.1);
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1]; // 解鎖并把conditoion設(shè)置為1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
-
打印結(jié)果:
image.png
分析:
- 有三個(gè)
并行隊(duì)列+異步函數(shù)
抄罕,分別處理三個(gè)任務(wù)
,三個(gè)任務(wù)
的執(zhí)行順序無(wú)序
于颖。
(并行隊(duì)列+異步線程
是的執(zhí)行順序
是不固定的呆贿,取決于任務(wù)資源大小
和cpu的調(diào)度
)- 我們init時(shí),將condition設(shè)置為2。
- 任務(wù)1: 必須當(dāng)前線程
沒(méi)被鎖
做入,且condition
為1
時(shí)冒晰,我才加鎖
并執(zhí)行后面代碼
。- 任務(wù)2: 必須當(dāng)前線程
沒(méi)被鎖
竟块,且condition
為2
時(shí)壶运,我才加鎖
并執(zhí)行后面代碼
。- 任務(wù)3: 必須當(dāng)前線程
沒(méi)被鎖
浪秘,我可以加鎖
并執(zhí)行后面代碼
蒋情。所以
任務(wù)3
的執(zhí)行時(shí)期
并不確定
,只要當(dāng)前線程沒(méi)被鎖
秫逝,隨時(shí)都可以恕出。任務(wù)1
一定在任務(wù)2
的后面
:
- 因?yàn)?code>condition初始值為
2
,只有任務(wù)2
滿足條件违帆,任務(wù)2執(zhí)行完
后浙巫,將condition
設(shè)置為1
,并broadcast
廣播給所有等待的線程
刷后。- 此時(shí)
正在等待
的任務(wù)1
的線程收到廣播
的畴,檢查任務(wù)1
,滿足條件
尝胆,任務(wù)1
執(zhí)行完后丧裁,將condition
設(shè)置為0
,并broadcast
廣播給所有等待的線程
含衔。
-
Swift Foundation
源碼中搜索NSConditionLock
煎娇,可以看到循環(huán)檢查線程、條件
和上鎖過(guò)程
:
image.png
感興趣的贪染,我們可以
匯編驗(yàn)證
下部分流程
:
(匯編
是機(jī)器執(zhí)行
的代碼
缓呛,是最準(zhǔn)確
的執(zhí)行順序
。找不到源碼
時(shí)杭隙,只有它才是最有效
的探索路徑
)(PS:
匯編
確實(shí)很難懂
哟绊,這里只是簡(jiǎn)單介紹
一下部分流程
,主要是思路的拓寬
)
- 簡(jiǎn)化測(cè)試代碼:
- (void)demo{ NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ [conditionLock lockWhenCondition:2]; // conditoion = 2 內(nèi)部 Condition 匹配 NSLog(@"線程 2"); [conditionLock unlockWithCondition:1]; // 解鎖并把conditoion設(shè)置為1 }); }
lockWhenCondition
加上斷點(diǎn)
痰憎,打開(kāi)匯編
模式:image.png
image.png
運(yùn)行
代碼票髓,執(zhí)行到斷點(diǎn)
處,再加入lockWhenCondition:
符號(hào)斷點(diǎn):(注意:冒號(hào)不能少
铣耘,前后
不能有空格
)洽沟,再運(yùn)行代碼
:
image.png在
lockWhenCondition:beforeDate:
一行加斷點(diǎn)
,運(yùn)行
至此處蜗细,讀取參數(shù)
玲躯,發(fā)現(xiàn)beforeDate
的默認(rèn)值是distantFuture
:
image.png加入
lockWhenCondition:beforeDate:
符號(hào)斷點(diǎn),運(yùn)行代碼,進(jìn)入到該函數(shù)內(nèi):
image.png發(fā)現(xiàn)首先調(diào)了
lock
函數(shù)跷车,我們加入lock
斷點(diǎn),運(yùn)行
代碼橱野,發(fā)現(xiàn)內(nèi)部是NSCondition
執(zhí)行了lock
方法:
image.png回到上一頁(yè)朽缴,我們?cè)?code>pthread_equal下一行加入斷點(diǎn),運(yùn)行代碼水援。打印相應(yīng)值:
image.png
pthread_equal
檢查線程是否存在密强,true
:跳到0x7fff207ef545
,false
:比較r15
和rbx
偏移0x10
位蜗元。
這里實(shí)際就是檢查線程是否存在或渤,如果不存在,再檢查condition
是否相等奕扣。才進(jìn)行后續(xù)操作... 大概思路就是這樣... 講個(gè)思路就行薪鹦。 真正的匯編探索,還需要
很大的基本功
和海量訓(xùn)練
惯豆。
關(guān)于鎖的探索
池磁,到此為止。 其他類型的鎖楷兽,可以用類似方式
去探索
和研究
地熄。