iOS 鎖的底層分析(2)--各種的鎖使用分析

前言

上一篇文章重點(diǎn)講解了@synchronized的使用以及其底層原理,其實(shí)iOS開發(fā)中還提供了其他鎖讓我們使用,那么現(xiàn)在就開始來分析探索各種所的使用。

準(zhǔn)備工作

1. NSLock和NSRecursiveLock的使用

首先我們引入下面的案例:

- (void)lg_testRecursive{
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
            };
            testMethod(10);
        });
    }
}

運(yùn)行結(jié)果如下:

運(yùn)行結(jié)果

其中testMethod靜態(tài)block,因?yàn)?code>多線程的影響,所以運(yùn)行結(jié)果錯(cuò)亂無序的,但是這個(gè)案例還是出現(xiàn)了混亂的過程,這是什么回事呢?又有什么解決的辦法呢?請(qǐng)繼續(xù)往下。
案例分析

加載遞歸里面:
案例分析

在分析@synchronized時(shí)知道,數(shù)據(jù)結(jié)構(gòu)SyncData中分裝了recursive_mutex_t遞歸互斥鎖,@synchronized是一個(gè)支持多線程遞歸的鎖。

1.1 NSLock

使用NSLock進(jìn)行加鎖,將鎖加在業(yè)務(wù)代碼外層,修改后代碼如下:

案例分析

好明顯 在業(yè)務(wù)代碼外層使用NSLock是可以的,那么在業(yè)務(wù)代碼中使用NSLock加鎖呢?請(qǐng)往下看:
案例分析

此時(shí)的輸出被阻塞了,這是為什么呢?因?yàn)樵谡{(diào)用textMethod方法之后,lock加鎖,內(nèi)部又繼續(xù)調(diào)用testMethod,導(dǎo)致重復(fù)加鎖。

注意:這里說明NSLock是不支持遞歸加鎖!

1.2 NSRecursiveLock

同樣的流程,在業(yè)務(wù)代碼外部使用NSRecursiveLock進(jìn)行加鎖,如下:

案例分析

由運(yùn)行結(jié)果可以看出,使用NSRecursiveLock在業(yè)務(wù)代碼外層加鎖是可以的,那么放在業(yè)務(wù)代碼內(nèi),請(qǐng)往下看:
案例分析

通過上面的運(yùn)行結(jié)果發(fā)現(xiàn),NSRecursiveLock在完成一次業(yè)務(wù)操作后就崩潰了。

注意:以上說明NSRecursiveLock支持單線程內(nèi)的遞歸加鎖,但是并不支持多線程遞歸

2. NSCondition的使用

NSCondition的對(duì)象實(shí)際上作為?個(gè)鎖?個(gè)線程檢查器:鎖主要為了當(dāng)檢測(cè)條件時(shí)保護(hù)數(shù)據(jù)源,執(zhí)?條件引發(fā)的任務(wù);線程檢查器主要是根據(jù)條件決定是否繼續(xù)運(yùn)?線程,即線程是否被阻塞。

2.1 NSCondition提供的API

  • [condition lock]; ?般?于多線程同時(shí)訪問、修改同?個(gè)數(shù)據(jù)源,保證在同?時(shí)間內(nèi)數(shù)據(jù)源只被訪問、修改?次,其他線程的命令需要在lock外等待,只到unlock,才可訪問。
  • [condition unlock];lock同時(shí)使?。
  • [condition wait];讓當(dāng)前線程處于等待狀態(tài)。
  • [condition signal]; CPU發(fā)信號(hào)告訴線程不?在等待,可以繼續(xù)執(zhí)?

2.2 案例分析

模擬生產(chǎn)和消費(fèi)的需求,開啟多個(gè)線程進(jìn)行產(chǎn)品生產(chǎn),同時(shí)開啟多個(gè)線程進(jìn)行銷售產(chǎn)品。見下面案例:

#pragma mark **-- NSCondition**
- (void)lg_testConditon{
    NSCondition *testCondition = [[NSCondition alloc] init];

    // 創(chuàng)建-生產(chǎn)者
    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_producer];
        });
    }
    
    // 創(chuàng)建-消費(fèi)者
    for (int i = 0; i < 50; i++) {
        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];
        });
    }
}

- (void)lg_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) lg_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];
}

注意:
生產(chǎn)線、消費(fèi)線需要進(jìn)行加鎖處理,以保證多線程安全,于此同時(shí),生產(chǎn)和消費(fèi)之前也存在關(guān)系,比如庫存數(shù)的安全!通過[_testCondition wait];模擬庫存不足,讓消費(fèi)窗口停止消費(fèi);用[_testCondition signal];模擬已有庫存可以消費(fèi),向等待的線程發(fā)送信號(hào),開始執(zhí)行。

運(yùn)行結(jié)果如下:


運(yùn)行結(jié)果

3. Foundation源碼了解鎖的封裝

NSLockNSCondtion、NSRecursiveLock底層都是對(duì)pthread的封裝。它們的底層實(shí)現(xiàn)都封裝在了OC環(huán)境下的Foundation框架中,由于Foundation框架不開源,那么我們只能從它的聲明部分進(jìn)行簡(jiǎn)單的分析探索。

鎖的聲明1

鎖的聲明2

那么看了上面的生面,可以知道定義了一個(gè)NSLocking協(xié)議,并且提供了lockunlock方法。所以我們使用的?如條件鎖,遞歸鎖都會(huì)有對(duì)應(yīng)的lock方法和unlock方法。

  • NSLock的底層分析
    源碼中全局搜索NSLock:,查到NSLock鎖定義的地方,見下圖:

    NSLock定義

    然后進(jìn)入定義中的pthread_mutex_init函數(shù),如下:
    pthread_mutex_init

    得出:
    lock方法中,調(diào)用了pthread_mutex_lock函數(shù);unlock方法中,調(diào)用了pthread_mutex_unlock函數(shù)。底層就是對(duì)pthread的封裝。

  • NSRecursiveLock的底層分析
    采用相同的方式,搜索遞歸鎖NSRecursiveLock,如下:

    NSRecursiveLock定義

    然后進(jìn)入pthread_mutex_init方法,都是一樣的。如下:
    pthread_mutex_init

    得出:
    發(fā)現(xiàn)其也是對(duì)pthread的封裝,并且通過在init方法中,通過pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))進(jìn)行遞歸設(shè)置。

  • NSCondition的底層分析
    NSCondition底層也是對(duì)pthread進(jìn)行了封裝,見下圖:

    NSCondition定義

    NSCondition其他操作

    得出:
    除了進(jìn)行pthread_mutex互斥處理外,還對(duì)pthread_cond進(jìn)行了處理,同時(shí)提供了wait、signal、broadcase方法。底層同樣對(duì)phread的封裝。

  • NSConditionLock的底層分析
    NSConditionLock底層沒有直接操作pthread_mutex,如下:

    NSConditionLock定義

    但是NSConditionLock實(shí)現(xiàn)中提供了一個(gè)NSCondition屬性和一個(gè)pthread_t屬性,通過這兩個(gè)屬性,實(shí)現(xiàn)加鎖線程方面的相關(guān)處理。所以也可以推斷出NSConditionLock也是由phread封裝得來的。

4. NSConditionLock的使用

NSConditionLock也是一種條件鎖,?旦?個(gè)線程獲得鎖,其他線程?定等待。

4.1 NSConditionLock相關(guān)的API

  • [xxxx lock];表示xxx期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內(nèi)部的condition) 那它能執(zhí)?此?以下代碼,如果已經(jīng)有其他線程獲得鎖(可能是條件鎖,或者?條件鎖),則等待,直?其他線程解鎖。
  • [xxx lockWhenCondition:A條件]; 表示如果沒有其他線程獲得該鎖,但是該鎖內(nèi)部的condition不等于A條件,它依然不能獲得鎖,仍然等待。如果內(nèi)部的condition等于A條件,并且沒有其他線程獲得該鎖,則進(jìn)?代碼區(qū),同時(shí)設(shè)置它獲得該鎖,其他任何線程都將等待它代碼的完成,直?它解鎖。
  • [xxx unlockWithCondition:A條件]; 表示釋放鎖,同時(shí)把內(nèi)部的condition設(shè)置為A條件。
  • return = [xxx lockWhenCondition:A條件 beforeDate:A時(shí)間];表示如果被鎖定(沒獲得鎖),并超過該時(shí)間不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態(tài),這個(gè)函數(shù)的?的在于可以實(shí)現(xiàn)兩種狀態(tài)下的處理。

4.2 案例分析

案例如下:

- (void)lg_testConditonLock{

    // 創(chuàng)建條件鎖 - 需要滿足條件2,否則不執(zhí)行
    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];
    });
}

運(yùn)行結(jié)果見下圖:


運(yùn)行結(jié)果

分析結(jié)果:

  • NSConditionLock創(chuàng)建時(shí),設(shè)置的條件時(shí)2,也就是說需要滿足條件2,否則不執(zhí)行;
  • 線程 1調(diào)?[NSConditionLock lockWhenCondition:1],此時(shí)因?yàn)椴粷M?當(dāng)前條件,所以會(huì)進(jìn)?waiting狀態(tài),當(dāng)前進(jìn)?到waiting時(shí),會(huì)釋放當(dāng)前的互斥鎖。
  • 此時(shí)當(dāng)前的線程 3調(diào)?[NSConditionLock lock:],本質(zhì)上是調(diào)?[NSConditionLock lockBeforeDate:],這?不需要?對(duì)條件值,所以線程 3會(huì)打印。
  • 接下來線程 2執(zhí)?[NSConditionLock lockWhenCondition:2],因?yàn)闈M?條件值,所以線程 2會(huì)打印,打印完成后會(huì)調(diào)?[NSConditionLock unlockWithCondition:1],這個(gè)時(shí)候?qū)?code>value設(shè)置為1,并發(fā)送boradcast, 此時(shí)線程 1接收到當(dāng)前的信號(hào),喚醒執(zhí)?并打印。
  • ?此當(dāng)前打印為 線程 3->線程 2->線程 1;
  • [NSConditionLock lockWhenCondition:]:這?會(huì)根據(jù)傳?的condition值和Value值進(jìn)?對(duì)?,如果不相等,這?就會(huì)阻塞,進(jìn)?線程池,否則的話就繼續(xù)代碼執(zhí)?
  • [NSConditionLock unlockWithCondition:]: 這?會(huì)先更改當(dāng)前的value值,然后進(jìn)??播,喚醒當(dāng)前的線程。

5. 讀寫鎖的實(shí)現(xiàn)

5.1 讀寫鎖概念的分析理解

讀寫鎖實(shí)際是?種特殊的?旋鎖,它把對(duì)共享資源的訪問者劃分成讀者和寫者,讀者只對(duì)共享資源進(jìn)?讀訪問,寫者則需要對(duì)共享資源進(jìn)?寫操作。

這種鎖相對(duì)于?旋鎖??,能提?并發(fā)性,因?yàn)樵诙嗵幚砥飨到y(tǒng)中,它允許同時(shí)有多個(gè)讀者來訪問共享資源,最?可能的讀者數(shù)為實(shí)際的邏輯CPU數(shù)。寫者是排他性的,?個(gè)讀寫鎖同時(shí)只能有?個(gè)寫者或多個(gè)讀者(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者?有寫者。在讀寫鎖保持期間也是搶占失效的。

?次只有?個(gè)線程可以占有寫模式的讀寫鎖, 但是可以有多個(gè)線程同時(shí)占有讀模式的讀寫鎖. 正是因?yàn)檫@個(gè)特性,當(dāng)讀寫鎖是寫加鎖狀態(tài)時(shí), 在這個(gè)鎖被解鎖之前, 所有試圖對(duì)這個(gè)鎖加鎖的線程都會(huì)被阻塞.當(dāng)讀寫鎖在讀加鎖狀態(tài)時(shí), 所有試圖以讀模式對(duì)它進(jìn)?加鎖的線程都可以得到訪問權(quán), 但是如果線程希望以寫模式對(duì)此鎖進(jìn)?加鎖, 它必須直到所有的線程釋放鎖.

通常, 當(dāng)讀寫鎖處于讀模式鎖住狀態(tài)時(shí), 如果有另外線程試圖以寫模式加鎖, 讀寫鎖通常會(huì)阻塞隨后的讀模式鎖請(qǐng)求, 這樣可以避免讀模式鎖?期占?, ?等待的寫模式鎖請(qǐng)求?期阻塞.讀寫鎖適合于對(duì)數(shù)據(jù)結(jié)構(gòu)的讀次數(shù)?寫次數(shù)多得多的情況. 因?yàn)? 讀模式鎖定時(shí)可以共享, 以寫模式鎖住時(shí)意味著獨(dú)占, 所以讀寫鎖?叫共享-獨(dú)占鎖.

5.2 用到的API

  • pthread_rwlock_t lock; // 結(jié)構(gòu)
  • pthread_rwlock_init(&lock, null); // 初始化
  • pthread_rwlock_rdlock(&lock); // 讀加鎖
  • pthread_rwlock_tryrdlock(&lock); // 讀嘗試加鎖
  • pthread_rwlock_wdlock(&lock); // 寫加鎖
  • pthread_rwlock_trywdlock(&lock); // 寫嘗試加鎖
  • pthread_rwlock_unlock(&lock); // 解鎖
  • pthread_rwlock_destory(&lock); // 銷毀

5.3 pthread_rwlock_t的使用

引入下面的案例,開啟十個(gè)線程,同時(shí)進(jìn)行讀寫操作, 要求:

  • 可以實(shí)現(xiàn)多讀,多讀不互斥
  • 單寫,讀寫互斥
  • 寫寫互斥

實(shí)現(xiàn)代碼如下:

#import <Pthread.h>

@interface ViewController ()

@property (nonatomic, assign) NSUInteger ticketCount;
@property (nonatomic,assign) pthread_rwlock_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.ticketCount = 0;
    [self rwTest];
}

- (void)rwTest {
    // 初始化
    pthread_rwlock_init(&_lock, NULL);
    // 全局隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 開啟讀寫
    for (int i = 0; i<10; i++) {

        dispatch_async(queue, ^{
            [self read];
        });

        dispatch_async(queue, ^{
            [self read];
        });
        
        // 寫
        dispatch_async(queue, ^{
            [self write];
        });
    }
}

// 讀流程
-(void)read{
    // 讀加鎖
    pthread_rwlock_rdlock(&_lock);

    sleep(1);
    NSLog(@"讀……%zd", self.ticketCount);
    
    // 解鎖
    pthread_rwlock_unlock(&_lock);
}

// 寫
-(void)write{
    // 寫加鎖
    pthread_rwlock_wrlock(&_lock);

    sleep(1);
    NSLog(@"寫……%zd", ++self.ticketCount);

    // 解鎖
    pthread_rwlock_unlock(&_lock);
}

@end

運(yùn)行結(jié)果如下:

運(yùn)行結(jié)果

通過上面的案例可以反映出讀寫鎖同時(shí)只能有?個(gè)寫者,并且可以保證多讀同時(shí)進(jìn)行。

5.4 GCD柵欄函數(shù)的使用來實(shí)現(xiàn)讀寫鎖

實(shí)現(xiàn)代碼如下:

#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
// 并發(fā)隊(duì)列-多讀
@property (nonatomic, strong) dispatch_queue_t qCONCURRENT;
// 串行隊(duì)列-限制讀取順序
@property (nonatomic, strong) dispatch_queue_t qSERIAL;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.ticketCount = 0;

    // 隊(duì)列初始化
    self.qCONCURRENT = dispatch_queue_create("selfCONCURRENT", DISPATCH_QUEUE_CONCURRENT);

    self.qSERIAL = dispatch_queue_create("selfSERIAL", DISPATCH_QUEUE_SERIAL);

    [self go_testReadWrite];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 寫  柵欄函數(shù),保證讀寫的互斥
    dispatch_barrier_async(self.qCONCURRENT, ^{
        [self writeAction];
    });
}

#pragma read wirte
- (void)go_testReadWrite{
    // 多線程讀
    for (int i = 0; i < 2000; i++) {
        dispatch_async(self.qCONCURRENT, ^{
            [self readAction];
        });

        dispatch_async(self.qCONCURRENT, ^{
            [self readAction];
        });

        dispatch_async(self.qCONCURRENT, ^{
            [self readAction];
        });
    }
}

- (void)readAction {

    // 保證讀取順序
    dispatch_async(self.qSERIAL, ^{
        sleep(1);
        NSLog(@"讀 ..... %ld ------ %@", self.ticketCount, [NSThread currentThread]);
    });
}

- (void)writeAction {
    sleep(1);
    NSLog(@"寫 ..... %ld ------ %@", ++self.ticketCount, [NSThread currentThread]);
}

運(yùn)行結(jié)果如下:


運(yùn)行結(jié)果

通過案例發(fā)現(xiàn)讀寫鎖的實(shí)現(xiàn)還是有多種的方式的。

總結(jié)

OC中鎖的探索就到此結(jié)束了,在學(xué)習(xí)過程中收獲滿滿。這對(duì)我們?cè)陂_發(fā)過程中保證線程的安全有很大的幫助哦。

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

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

  • 1、鎖的歸類 鎖的分類只有兩大類自旋鎖和和互斥鎖。這兩大類下又分成很多不同的小類。了解鎖之前建議先了解一下線程及線...
    希爾羅斯沃德_董閱讀 2,167評(píng)論 2 4
  • 一、線程鎖相關(guān)概念 線程鎖:我們?cè)谑褂枚嗑€程的時(shí)候多個(gè)線程可能會(huì)訪問同一塊資源,這樣就很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全...
    2525252472閱讀 484評(píng)論 0 2
  • 概念 自旋鎖: 線程反復(fù)檢查鎖變量是否可用。由于線程在這一過程中保持執(zhí)行, 因此是一種忙等待。一旦獲取了自旋鎖,線...
    MonKey_Money閱讀 1,010評(píng)論 2 1
  • 轉(zhuǎn)鏈接:https://juejin.im/post/5d395318f265da1b8608ca98 自旋鎖 O...
    DL是誰閱讀 543評(píng)論 0 0
  • 手動(dòng)目錄什么是鎖鎖的工作機(jī)制鎖的分類設(shè)計(jì)到鎖的其他概念常用鎖的用法@synchronized()NSLock信號(hào)量...
    Engandend閱讀 575評(píng)論 0 2

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