上篇文章介紹synchronized鎖汗侵,今天介紹的是其他常用鎖:NSLock,NSRecursiveLock ,NSCondition昆箕,NSConditionLock
鎖的概念
鎖的分類——互斥鎖鸦列,自旋鎖,讀寫鎖
自旋鎖
自旋鎖是一種用于保護(hù)多線程共享資源的鎖鹏倘,與一般互斥鎖(mutex)不同之處在于當(dāng)它嘗試獲取鎖時(shí)以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用敛熬。當(dāng)上一個(gè)線程的任務(wù)沒(méi)有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)一直等待(不會(huì)睡眠)第股,當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢应民,下一個(gè)線程會(huì)立即執(zhí)行。在多CPU的環(huán)境中夕吻,對(duì)持有鎖較短的程序來(lái)說(shuō)诲锹,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。
優(yōu)點(diǎn):自旋鎖不會(huì)引起調(diào)用者睡眠涉馅,所以不會(huì)進(jìn)行線程調(diào)度归园、CPU時(shí)間片輪轉(zhuǎn)等耗時(shí)操作。所有如果能在很短的時(shí)間內(nèi)獲得鎖稚矿,自旋鎖的效率遠(yuǎn)高于互斥鎖
缺點(diǎn):自旋鎖一直占用CPU庸诱,他在未獲得鎖的情況下一直運(yùn)行(自旋)占用著CPU,如果不能在很短的時(shí)間內(nèi)獲得鎖晤揣,這無(wú)疑會(huì)使CPU效率降低
總結(jié):效率高桥爽,但是一直占用CPU耗費(fèi)資源,不能實(shí)現(xiàn)遞歸調(diào)用昧识。
互斥鎖
什么是互斥鎖呢钠四?
- 當(dāng)上一個(gè)線程的任務(wù)沒(méi)有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)進(jìn)入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢跪楞,此時(shí)CPU可以調(diào)度其他線程缀去。當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢,下一個(gè)線程會(huì)自動(dòng)喚醒然后執(zhí)行任務(wù)甸祭。
- 說(shuō)到互斥后想到同步缕碎,同步是只有一個(gè)任務(wù)執(zhí)行完了,下個(gè)任務(wù)才可以執(zhí)行池户。
同步:互斥+順序
常見(jiàn)的互斥鎖:@synchronized
咏雌,NSLock
岳锁,pthread_mutex
荸百,NSConditionLock
(條件鎖)鲜戒,NSCondition
(條件鎖)粒梦,NSRecursiveLock
(遞歸鎖)
以上概念轉(zhuǎn)載自:https://juejin.cn/post/6999520963658268709/
鎖的歸類
條件鎖:就是條件變量擎场,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠书妻,也就是鎖住了啼县,當(dāng)資源被分配到了衰絮,條件鎖打開凝赛,進(jìn)程繼續(xù)運(yùn)行
- NSCondition
- NSConditionLock
遞歸鎖:就是同一個(gè)線程可以加鎖N次而不會(huì)引發(fā)死鎖
- NSRecursiveLock
- pthread_mutext(recursive)
信號(hào)量:是一種更高級(jí)的同步機(jī)制注暗,互斥鎖可以說(shuō)是semaphore在僅取值0/1時(shí)的特例坛缕。信號(hào)量可以有更多的取值空間,用來(lái)實(shí)現(xiàn)更加復(fù)雜的同步捆昏,而不單單是線程間互斥赚楚。
- dispatch_semaphore
讀寫鎖
讀寫鎖實(shí)際是一種特殊的互斥鎖,他把對(duì)共享資源的訪問(wèn)分為讀者和寫者骗卜,讀者只有對(duì)共享資源的讀權(quán)限宠页,寫著可以對(duì)共享資源做寫處理。所以一般我們?cè)谧x的時(shí)候不會(huì)去加互斥鎖寇仓,允許多個(gè)讀者進(jìn)行訪問(wèn)举户,但是如果有遇到寫者訪問(wèn)資源,這時(shí)候必須對(duì)資源進(jìn)行加鎖遍烦,等其訪問(wèn)結(jié)束后才能繼續(xù)讀/寫
特性:
- 多個(gè)讀者同時(shí)訪問(wèn)
- 讀與寫互斥
- 寫和寫互斥即單寫
上面都是一些概念俭嘁,接下來(lái)就開始詳細(xì)分析常用的鎖
NSLock,NSRecursiveLock
簡(jiǎn)單的鎖服猪,不能夠遞歸嵌套也不能多線程
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
// [lock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
// [lock unlock];
};
[lock lock];
testMethod(10);
[lock unlock];
});
}
}
輸出:
這邊我們的鎖直接加在testMethod(10);
等線程這邊遞歸完成后再繼續(xù)下個(gè)線程供填,但是有時(shí)候我們可能會(huì)加在遞歸函數(shù)里面,如上面的注釋罢猪,那就會(huì)造成死鎖近她,為什么會(huì)造成死鎖呢?在testMethod剛進(jìn)去加鎖坡脐,但是testMethod還沒(méi)出來(lái)又嵌套進(jìn)入到testMethod里又加鎖泄私,就造成了死鎖,解決辦法就是使用嵌套鎖NSRecursiveLock/synchronized
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[self.recursiveLock lock];
if (value > 0) {
NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
testMethod(value - 1);
}
[self.recursiveLock unlock];
};
testMethod(10);
});
}
}
輸出結(jié)果:
current value = 10 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 9 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 8 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 7 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 6 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 5 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 4 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 3 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 2 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 1 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
這邊使用NSRecursiveLock
嘗試解決备闲,但是很遺憾還是崩,只是崩潰的不一樣捅暴,最后輸出的結(jié)果如上圖恬砂,為什么呢?因?yàn)?code>NSRecursiveLock不支持多線程蓬痒,如果只開一個(gè)線程就不會(huì)崩泻骤,但是換成synchronized就OK
- (void)testRecursive{
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<2; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
testMethod(value - 1);
}
}
};
testMethod(10);
});
}
}
輸出結(jié)果:
current value = 10 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 9 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 8 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 7 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 6 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 5 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 4 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 3 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 2 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 1 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 10 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 9 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 8 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 7 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 6 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 5 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 4 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 3 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 2 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 1 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
接下來(lái)我們來(lái)看看NSLock
和NSRecursiveLock
的源碼,因?yàn)?code>NSLock和NSRecursiveLock
是在Foundation
框架梧奢,這個(gè)是不開源的但是swift
的Foundation
框架是開源的狱掂,我們可以看看swift
的Foundation
框架
通過(guò)源碼就知道NSLock就是對(duì)mutex的封裝
這個(gè)也是對(duì)mutex的封裝,但是在初始化跟NSlock有所不同亲轨,
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
添加了嵌套屬性
NSCondition和NSConditionLock
NSCondition條件鎖主要運(yùn)用在生產(chǎn)著消費(fèi)者模型
- (void)testConditon{
_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 lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多線程影響
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal]; // 信號(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];
}
當(dāng)消費(fèi)者消費(fèi)到?jīng)]有物品時(shí)就會(huì)處于等待,而生產(chǎn)者每生產(chǎn)一個(gè)產(chǎn)品都會(huì)通知一下消費(fèi)者惦蚊,讓那些處于等待狀態(tài)的消費(fèi)者及時(shí)消費(fèi)器虾。
底層是對(duì)cond的封裝
NSConditionLock 的使用
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
輸出:
線程 3
線程 2
線程 1
通過(guò)lockWhenCondition
控制線程的執(zhí)行順序讯嫂,這邊預(yù)先設(shè)置condition為2,因?yàn)榫€程2的條件是[conditionLock lockWhenCondition:2]
滿足條件會(huì)比線程1優(yōu)先執(zhí)行兆沙,執(zhí)行完[conditionLock unlockWithCondition:1];
設(shè)置為1欧芽,這樣線程1就滿足條件,等待cpu調(diào)度
葛圃。
讀寫鎖
之前介紹的讀寫鎖千扔,多個(gè)線程可以同時(shí)對(duì)資源進(jìn)行讀操作不用加鎖,但是寫操作是需要加鎖库正。這樣我們就可以通過(guò)GCD的柵欄函數(shù)來(lái)實(shí)現(xiàn)
- (id)init{
self = [super init];
if (self){
// 創(chuàng)建一個(gè)并發(fā)隊(duì)列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建數(shù)據(jù)字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (void)getObjc {
for (int i=0; i<5; i++) {
dispatch_async(self.concurrent_queue, ^{
// 多個(gè)線程讀取數(shù)據(jù)
[self objectForKey:@"a"];
});
}
}
#pragma mark - 讀數(shù)據(jù)
- (id)objectForKey:(NSString *)key{
__block id obj;
// 同步讀取指定數(shù)據(jù):
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 寫數(shù)據(jù)
- (void)setObject:(id)obj forKey:(NSString *)key{
// 異步柵欄調(diào)用設(shè)置數(shù)據(jù):
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
注意:同步讀取指定數(shù)據(jù)之所以要加dispatch_sync
是為了滿足讀和寫的互斥