前言
這篇文章,記錄幾種鎖的簡單應(yīng)用。
@synchronized
使用起來最簡單的一個鎖,直接將要鎖定的代碼用@synchronized包裹,如下:
- (void)demo33
{
for (int i = 0; i < 100000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (self) {
self.testArray = [NSMutableArray array];
}
});
}
}
需要注意的是:
-
@synchronized的參數(shù)在使用期間不能為nil - 性能問題
NSLock
NSLock 互斥鎖的一種,性能高于@synchronized,使用也比較簡單
- (void)demo33
{
NSLock *lock = [[NSLock alloc]init];
for (int i = 0; i < 100000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
self.testArray = [NSMutableArray array];
[lock unlock];
});
}
}
注意,當(dāng)存在嵌套遞歸的情況時(shí),比如下面的代碼:
NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<100; 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];
};
testMethod(10);
});
}
這里執(zhí)行時(shí)會發(fā)現(xiàn)有問題了,當(dāng)在嵌套遞歸的情況下,單個線程內(nèi)的lock會發(fā)生死鎖;而線程與線程之間發(fā)生循環(huán)等待任務(wù);從而導(dǎo)致無法正常執(zhí)行下去,那么接下看另外一個鎖。
NSRecursiveLock
從類名上看,NSRecursiveLock 是一個遞歸鎖,我們用上面的案例,然后替換使用NSRecursiveLock看下:
- (void)demo44
{
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
[recursiveLock lock];
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
}
執(zhí)行后發(fā)現(xiàn):
2020-11-10 17:39:09.416482+0800 TestApp[47016:8556099] current value = 10
2020-11-10 17:39:09.416671+0800 TestApp[47016:8556099] current value = 9
2020-11-10 17:39:09.416811+0800 TestApp[47016:8556099] current value = 8
2020-11-10 17:39:09.416952+0800 TestApp[47016:8556099] current value = 7
2020-11-10 17:39:09.417089+0800 TestApp[47016:8556099] current value = 6
2020-11-10 17:39:09.417214+0800 TestApp[47016:8556099] current value = 5
2020-11-10 17:39:09.417338+0800 TestApp[47016:8556099] current value = 4
2020-11-10 17:39:09.417461+0800 TestApp[47016:8556099] current value = 3
2020-11-10 17:39:09.417590+0800 TestApp[47016:8556099] current value = 2
2020-11-10 17:39:09.417858+0800 TestApp[47016:8556099] current value = 1

這里由于我們的
[recursiveLock lock];位置不對,導(dǎo)致線程之間發(fā)生循環(huán)等待,從而導(dǎo)致線程間的死鎖,我們調(diào)整下lock的位置:
- (void)demo44
{
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
[recursiveLock lock];
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
[recursiveLock unlock];
};
testMethod(10);
});
}
}
然后執(zhí)行結(jié)果正常。這里也就說明一個坑點(diǎn)就是,盡管NSRecursiveLock是一把支持遞歸嵌套場景的鎖,但是如果使用不當(dāng)還是會出現(xiàn)各種問題,所以這里的建議是如果業(yè)務(wù)邏輯簡單,直接使用@synchronized鎖(使用簡單,支持多線程遞歸嵌套);如果要使用NSRecursiveLock(性能高),就要注意使用的細(xì)節(jié)了。
NSCondition
iOS下一個簡單的條件鎖,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠,也就
是鎖住了。當(dāng)資源被分配到了,條件鎖打開,進(jìn)程繼續(xù)運(yùn)行。這里只做一些簡單的使用。條件鎖即生產(chǎn)者和消費(fèi)者的關(guān)系,比如賣包子和買包子,當(dāng)前賣家有包子可賣,買包子的就可以正常買;當(dāng)包子賣完時(shí),此時(shí)賣家暫停賣包子,開始蒸包子,買家也暫停買包子,開始等待;等賣家包子蒸好了,賣家通知買家可以買了,買家就能繼續(xù)買包子了。??
來看一個案例:
- (void)lg_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 producer]; //蒸包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; //買包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; //買包子
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; //蒸包子
});
}
}
- (void)producer{
[_testCondition lock]; // 操作的多線程影響
self.bunCount = self.bunCount + 1;
NSLog(@"蒸好一個 現(xiàn)有 count %zd",self.bunCount);
[_testCondition signal]; // 信號,賣家說可以買了
[_testCondition unlock];
}
- (void)consumer{
[_testCondition lock]; // 操作的多線程影響
if (self.bunCount == 0) { //沒包子了 買家進(jìn)入等待(即等待signal信號)
NSLog(@"等待 count %zd",self.bunCount);
[_testCondition wait];
}
//注意消費(fèi)行為,要在等待條件判斷之后 開始買
self.bunCount -= 1;
NSLog(@"消費(fèi)一個 還剩 count %zd ",self.bunCount);
[_testCondition unlock];
}
這里要注意的是由于多線程,這里無論是買還是賣都要加鎖(非常貼進(jìn)生活嘛),由于每次lock,unlock,signal,wait顯得非常的繁瑣,所以就衍生出了一個更加高級點(diǎn)的條件鎖NSConditionLock。
NSConditionLock
這個鎖本質(zhì)上跟NSCondition是一致的,只是將之前的繁瑣的操作給去掉了,對開發(fā)者更加的友好,通過一個condition來控制執(zhí)行。
#pragma mark -- NSConditionLock
- (void)testConditonLock{
// 信號量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; //if condition == 1 執(zhí)行
NSLog(@"任務(wù) 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2]; // if condition == 2 執(zhí)行
sleep(0.1);
NSLog(@"任務(wù) 2");
[conditionLock unlockWithCondition:1]; // condition == 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock]; // 沒有條件直接執(zhí)行
NSLog(@"任務(wù) 3");
[conditionLock unlock];
});
}
這里其實(shí)就是在加鎖的時(shí)候給了解鎖條件,NSConditionLock在初始化時(shí)給了一個默認(rèn)的條件:condition = 2, 這里的任務(wù)2滿足條件,會執(zhí)行;任務(wù)3不需要條件,也會執(zhí)行;由于異步并發(fā)的原因,任務(wù)2和任務(wù)3的執(zhí)行時(shí)沒有順序的,但任務(wù)1的執(zhí)行就依賴于任務(wù)2,任務(wù)1 只有滿足condition == 1的時(shí)候才能執(zhí)行,而這個需要等待任務(wù)2執(zhí)行完后,調(diào)用[conditionLock unlockWithCondition:1];將condition == 1,然后發(fā)出broadcast通知,讓滿足條件的任務(wù)去執(zhí)行。
dispatch_semaphore_t
dispatch_semaphore_t是GCD下的信號量鎖,這個鎖還是比較常用的,特別是在一些異步取值同步返回的操作中,比如:
- (int)demo777
{
__block int result = 0;
dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
result = 1+0+2+4;
dispatch_semaphore_signal(semphore); //釋放等待 value++
});
//進(jìn)入等待 value--
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
NSLog(@"result == %d",result);
return result;
}
當(dāng)value小于0時(shí)進(jìn)入等待,即dispatch_semaphore_wait()會做value--操作;
dispatch_semaphore_signal()會做value++操作。主要在使用時(shí),signal和wait是成對兒出現(xiàn)的。
總結(jié)
關(guān)于NSLock,NSCondition,NSConditionLock 都?xì)w屬于Foundation框架內(nèi)部,由于OC的Foundation沒有開源,如果想進(jìn)一步了解其內(nèi)部實(shí)現(xiàn),可以參考swift-Foundation。