iOS-幾種鎖的應(yīng)用

前言

這篇文章,記錄幾種的簡單應(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];
            }
        });
    }
}

需要注意的是:

  1. @synchronized的參數(shù)在使用期間不能為nil
  2. 性能問題
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

截屏2020-11-10 下午5.40.03.png

這里由于我們的[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_tGCD下的信號量鎖,這個鎖還是比較常用的,特別是在一些異步取值同步返回的操作中,比如:

- (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í),signalwait是成對兒出現(xiàn)的。

總結(jié)

關(guān)于NSLockNSCondition,NSConditionLock 都?xì)w屬于Foundation框架內(nèi)部,由于OC的Foundation沒有開源,如果想進(jìn)一步了解其內(nèi)部實(shí)現(xiàn),可以參考swift-Foundation。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 鎖是最常用的同步工具。一段代碼段在同一個時(shí)間只能允許被有限個線程訪問,比如一個線程 A 進(jìn)入需要保護(hù)代碼之前添加簡...
    沒八阿哥的程序閱讀 832評論 0 0
  • 前言 對于iOS中各種鎖的學(xué)習(xí)總結(jié),供日后查閱 引子 日常開發(fā)中,@property (nonatomic, st...
    Tr2e閱讀 982評論 1 1
  • 拋磚引玉 說到鎖不得不提線程安全,說到線程安全,作為iOS程序員又不得不提 nonatomic 與 atomic ...
    Inlight先森閱讀 2,173評論 0 23
  • 只要系統(tǒng)中存在多線程,存在共享資源,那么鎖就是一個繞不過去的概念,像后臺數(shù)據(jù)庫讀寫數(shù)據(jù)就要用到讀寫鎖,來保證數(shù)據(jù)的...
    憂郁的小碼仔閱讀 1,001評論 0 1
  • 可以看這篇文章:iOS 常見知識點(diǎn)(三):Lock 什么是鎖 鎖是一種同步機(jī)制,用于在存在多線程的環(huán)境中實(shí)施對資源...
    lxl125z閱讀 465評論 0 0

友情鏈接更多精彩內(nèi)容