iOS開發(fā)常用的幾種鎖!

前言

開發(fā)中引入了異步和多線程的來提高程序性能,也就意味著線程安全成為了多線程的一個(gè)障礙,因此線程鎖應(yīng)運(yùn)而生,而鎖如果用不好,還會造成死鎖的風(fēng)險(xiǎn)

下面就介紹ios中常用的幾種鎖,以及讀寫鎖的實(shí)現(xiàn)

案例demo

常見的多線程鎖

ios中常見的幾種鎖包括OSSpinLock、信號量(Semaphore)、pthread_mutex、NSLock、NSCondition、NSConditionLock、pthread_mutex(recursive)、NSRecursiveLock、synchronized

如下所示,為前輩們測試鎖性能的案例圖(實(shí)際可能會略有偏差):

由于OSSpinLock目前已經(jīng)不再安全,這里就放棄介紹,案例也把他給刪了??

我們再選鎖的時(shí)候,如果只是使用互斥鎖的效果,那么按照性能排序選擇靠前的即可,如果需要鎖的一些其他功能,那么根據(jù)需要選擇,不必過于局限于性能,畢竟實(shí)現(xiàn)功能與項(xiàng)目的維護(hù)也是非常重要的

其他鎖的使用如下所示

信號量(semaphore)

信號量實(shí)現(xiàn)加鎖功能與其他的略有不同,其通過一個(gè)信號值來決定是否阻塞當(dāng)前線程

wait操作可以使得信號量值減少1,signal使得信號量值增加1

當(dāng)wait操作使得信號量值小于0時(shí),則所在線程阻塞阻塞休眠,使用signal使得信號量增加時(shí),會順序喚醒阻塞線程,以此便可以實(shí)現(xiàn)加鎖功能,

- (void)semaphore {
    _semaphore = dispatch_semaphore_create(1);
}

//wait操作可以使得信號量值減少1,signal使得信號量值增加1
//當(dāng)信號量值小于0時(shí),則所在線程阻塞休眠,使用signal使得信號量增加時(shí),會順序喚醒阻塞線程
- (void)semaphoreUpdate {
    //wait 可以理解為加鎖操作,信號值小于0會休眠當(dāng)前wait所在線程
    //第二個(gè)參數(shù) forever 為永遠(yuǎn),可以自行設(shè)置一段超時(shí)時(shí)間,達(dá)到等待時(shí)間會自動解鎖
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

    wait和singnal中間的這部分代碼,即為線程安全代碼
    _money++;

    //signal 可以解鎖
    dispatch_semaphore_signal(_semaphore);
}

pthread互斥鎖

pthread互斥鎖是 pthread 庫中的一員,linux系統(tǒng)中中常用的庫,使用時(shí)需要手動import導(dǎo)入 #import <pthread/pthread.h>

其中有 pthread_mutex_trylock為嘗試加鎖,如果沒被加鎖,則會加鎖成功,并返回0,適用于一些優(yōu)先級比較低,間歇性調(diào)用的功能

注意:其他部分鎖也有trylock這個(gè)功能,例如 NSLock、NSRecursiveLock、NSConditionLock

#pragma mark --pthread互斥鎖
- (void)pthreadMutex {
    pthread_mutex_init(&_pMutexLock, NULL);
    //使用完畢后在合適的地方銷毀,例如dealloc
//    pthread_mutex_destroy(&_pMutexLock);
}

- (void)pthreadMutexUpdate {
    //加鎖代碼區(qū)間操作,避免多線程同時(shí)訪問
    pthread_mutex_lock(&_pMutexLock);
    _money++;
    //解鎖代碼區(qū)間操作
    pthread_mutex_unlock(&_pMutexLock);
}

- (void)pthreadMutexSub {
    //減少數(shù)值
    [NSThread detachNewThreadWithBlock:^{
        //數(shù)量大于100開始減少,假設(shè)是需要清理東西,這里減少數(shù)值
        while (self->_money > 10000) {
            //嘗試加鎖,如果能加鎖,則加鎖,返回零,否則返回不為零的數(shù)字
            //加鎖失敗休眠在執(zhí)行,避免搶奪資源,此任務(wù)優(yōu)先級間接降低
            //其他的一些鎖也有這功能,例如NSLock、NSRecursiveLock、NSConditionLock
            if (pthread_mutex_trylock(&self->_pMutexLock) == 0) {
                self->_money--;
                //解鎖
                pthread_mutex_unlock(&self->_pMutexLock);
            }else {
                [NSThread sleepForTimeInterval:1];
            }
        }
    }];
}

NSLock互斥鎖

首先作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS開發(fā)交流群:130 595 548,不管你是小白還是大牛都?xì)g迎入駐 ,讓我們一起進(jìn)步,共同發(fā)展?。ㄈ簝?nèi)會免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)

NSLock 遵循 NSLocking協(xié)議,是常見的互斥鎖之一,為 OC 框架中的 API,使用方便,據(jù)說是 pthread 封裝的鎖

tryLock 方法也是嘗試加鎖,成功返回true,失敗返回false

lockBeforeDate:(NSDate *)limit 在一個(gè)時(shí)間之間加鎖,可以理解為加鎖日期截止到指定時(shí)間,會自動解鎖(與信號量的等待功能一樣,這個(gè)是設(shè)置到指定時(shí)間)

#pragma mark --NSLock互斥鎖
- (void)NSLock {
    _lock = [[NSLock alloc] init];
}

- (void)NSLockUpdate {
    //加鎖代碼區(qū)間,避免多線程同時(shí)訪問
    [_lock lock];
    _money++;
    //解鎖代碼區(qū)間
    [_lock unlock];
}

NSCondition鎖

NSCondition 算是一個(gè)稍微重量級的鎖了,我理解為情景鎖(另一個(gè)原因區(qū)分條件鎖 NSConditionLock),適用于一些特殊場景,其也遵循 NSLocking協(xié)議,也屬于互斥鎖

并且再其基礎(chǔ)上,新增了信號量功能 waitsignal,即 等待 和 釋放 ,使用方式和 semaphore 一樣,可以通過信號量控制線程的阻塞和釋放,除此之外,還多了一個(gè)broadcast,其可以解除所有因 wait 阻塞的線程

如下所示,使用 NSCondition 實(shí)現(xiàn)了一個(gè)生產(chǎn)者和消費(fèi)者的案例(生產(chǎn)者和消費(fèi)者都是同一撥人,因此需要加鎖來實(shí)現(xiàn),而為了保證有錢了立刻買自己想買的東西,使用信號量,保證沒錢時(shí)阻塞等待,有錢時(shí)立即解放買買買)

其相當(dāng)于同時(shí)使用了NSLock 和 Semaphore 功能

#pragma mark --情景鎖NSCondition實(shí)現(xiàn)了NSLocking協(xié)議,支持默認(rèn)的互斥鎖lock、unlock
- (void)NSCondition {
    _condition = [[NSCondition alloc] init];
}

//情景鎖還加入了信號量機(jī)制,wait和signal,可以利用其完成生產(chǎn)消費(fèi)者模式的功能
//生產(chǎn)者: 媽爸掙了一天的錢,儲蓄值增加
- (void)conditionPlusMoney {
    [_condition lock];
    //信號量增加,有儲蓄了,可以開放花錢功能了
    if (_money++ < 0) {
        [_condition signal];    //釋放第一個(gè)阻塞的線程
        //[_condition broadcast]; //釋放所有阻塞的線程
    }
    [_condition unlock];
}
//消費(fèi)者,服務(wù)有儲蓄,拿到錢時(shí)立即解鎖花錢技能(money--)
- (void)conditionSubMoney {
    [_condition lock];
    if (_money == 0) {
        //信號量減少阻塞,打算買東西,卻沒錢了,停止花錢,等發(fā)工資再買東西
        [_condition wait];
    }
    //由于之前的wait,當(dāng)signal解鎖后,會走到這里,開始購買想買的東西,儲蓄值--
    _money--;
    [_condition unlock];
}

NSConditionLock

NSConditionLock 被稱為條件鎖,其遵循 NSLocking 協(xié)議,即具備正常的互斥鎖功能

此外加入了 條件語句,為其核心功能,即滿足指定條件才會解鎖,因此算是一個(gè)重量級的鎖了,其同時(shí)可以理解為 NSCondition 進(jìn)化版 ,如果你理解了 NSCondition生產(chǎn)者-消費(fèi)者模式,這個(gè)也會馬上就明白了其原理了

lockWhenCondition:(NSInteger)condition: 加鎖,當(dāng)條件condition為傳入的condition時(shí),方能解鎖

unlockWithCondition:(NSInteger)condition: 更新condition的值,并解鎖指定condition的鎖

下面使用一個(gè)異步隊(duì)列,來實(shí)現(xiàn)類似 NSOperation 設(shè)置的依賴關(guān)系,如下所示(打印結(jié)果1、4、3、2):

#pragma mark --條件鎖NSConditionLock,實(shí)現(xiàn)了NSLocking協(xié)議,支持默認(rèn)的互斥鎖lock、unlock
- (void)NSConditionLock {
    _conditionLock = [[NSConditionLock alloc] initWithCondition:1]; //可以更改值測試為0測試結(jié)果
    //加鎖,當(dāng)條件condition為傳入的condition時(shí),方能解鎖
    //lockWhenCondition:(NSInteger)condition
    //更新condition的值,并解鎖指定condition的鎖
    //unlockWithCondition:(NSInteger)condition
}

//多個(gè)隊(duì)列執(zhí)行條件鎖
//通過案例可以看出,通過條件鎖conditionLock可以設(shè)置線程依賴關(guān)系
//可以通過GCD設(shè)置一個(gè)具有依賴關(guān)系的任務(wù)隊(duì)列么
- (void)NSConditionLockUpdate {
    //創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = 
        dispatch_queue_create("測試NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        if ([self->_conditionLock tryLockWhenCondition:1]) {
            NSLog(@"第一個(gè)");
            //默認(rèn)初始conditon位1,所有能走到這里
            //然后解鎖后,并設(shè)置初始值為4,解鎖condition設(shè)定為4的線程
            [self->_conditionLock unlockWithCondition:4];
        }else {
            [self->_conditionLock lockWhenCondition:0];
            NSLog(@"第一個(gè)other");
            [self->_conditionLock unlockWithCondition:4];
        }
    });
    //由于開始初始化的conditon值為1,所以后面三個(gè)線程都不滿足條件
    //鎖定后直到condition調(diào)整為當(dāng)前線程的condition時(shí)方解鎖
    dispatch_async(queue, ^{
        //condition設(shè)置為3后解鎖當(dāng)前線程
        [self->_conditionLock lockWhenCondition:2];
        NSLog(@"第二個(gè)");
        //執(zhí)行完畢后解鎖,并設(shè)置condition為1,設(shè)置初始化默認(rèn)值,以便于下次使用
        [self->_conditionLock unlockWithCondition:1];
    });
    dispatch_async(queue, ^{
        //condition設(shè)置為3后解鎖當(dāng)前線程
        [self->_conditionLock lockWhenCondition:3];
        NSLog(@"第三個(gè)");
        //執(zhí)行完畢后解鎖,并設(shè)置condition為3,解鎖3
        [self->_conditionLock unlockWithCondition:2];
    });
    dispatch_async(queue, ^{
        //condition設(shè)置為4后解鎖當(dāng)前線程
        [self->_conditionLock lockWhenCondition:4];
        NSLog(@"第四個(gè)");
        //執(zhí)行完畢后解鎖,并設(shè)置condition為3,解鎖3
        [self->_conditionLock unlockWithCondition:3];
    });
}

上面的流程可以大致簡化為下面幾步:

1.創(chuàng)建一個(gè)異步隊(duì)列,以便于添加后續(xù)的任務(wù)依賴

2.逐步添加子任務(wù)模塊,分別在不同線程中,其有明確的依賴關(guān)系,即執(zhí)行順序?yàn)?1、4、3、2

3.使用 lockWhenCondition:開始設(shè)置依賴,將其任務(wù)解鎖的條件condition 設(shè)置為其特有的condition 號,以便于解鎖

4.執(zhí)行任務(wù)時(shí),如果 NSCondition 中的 condition 參數(shù),與本線程設(shè)置的tCondition不一樣時(shí),阻塞線程,等待 NSCondition 中的 condition 更改為指定值(通過 unlockWithCondition:更改condition值)解鎖

即:默認(rèn)初始化 condition 為 1,只有 任務(wù)1 能夠執(zhí)行,當(dāng) 任務(wù)1 執(zhí)行 unlockWithCondition:4時(shí),condition被設(shè)置為4, 阻塞的任務(wù)4解鎖,同理,任務(wù)4執(zhí)行完畢后,將 condition 設(shè)置為 3 ,任務(wù)三解鎖,依次類推

5.最終根據(jù)設(shè)置的依賴關(guān)系,分別執(zhí)行 任務(wù)1、任務(wù)4、任務(wù)3、任務(wù)2

pthread_mutex(recursive)

其為基于 pthread框架 的遞歸鎖,也是以 pthread互斥鎖為基礎(chǔ)實(shí)現(xiàn)的 遞歸鎖,即:同一個(gè)線程下,遞歸調(diào)用時(shí)加鎖,不會阻塞當(dāng)前線程,當(dāng)另一個(gè)線程到來時(shí),會因?yàn)榈谝粋€(gè)線程加的鎖而阻塞

#pragma mark --pthread遞歸鎖
- (void)pthreadMutexRecursive {
    //初始化鎖的遞歸功能
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    //互斥鎖初始化時(shí),綁定遞歸鎖功能模塊
    pthread_mutex_init(&_pMutexLock, &attr);

    //使用完畢后在合適的地方銷毀,例如dealloc
//    pthread_mutexattr_destroy(&attr);
//    pthread_mutex_destroy(&_pMutexLock);
}

//使用遞歸鎖,遞歸地時(shí)候回不停加鎖,如果使用普通的鎖早已經(jīng)形成死鎖,無法解脫
//遞歸鎖的存在就是在同一個(gè)線程中的鎖,不會互斥,只會互斥其他線程的鎖,從而避免死鎖
- (void)pthreadMutexRecursiveUpdate {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^recursiveBlock)(double count);
        recursiveBlock = ^(double count){
            pthread_mutex_lock(&self->_pMutexLock);
            if (count-- > 0) {
                self->_money++;
                recursiveBlock(count);
            }
            pthread_mutex_unlock(&self->_pMutexLock);
        };
        recursiveBlock(1000);
    });
}

NSRecursiveLock遞歸鎖

pthread_mutex(recursive)一樣,NSRecursiveLock 也是遞歸鎖,其遵循 NSLocking 協(xié)議,即除了遞歸鎖功能,還具備正常的互斥鎖功能

使用方式和 pthread_mutex(recursive)一樣如下所示

//使用遞歸鎖,遞歸地時(shí)候回不停加鎖,如果使用普通的鎖早已經(jīng)形成死鎖,無法解脫
//遞歸鎖的存在就是在同一個(gè)線程中的鎖,不會互斥,只會互斥其他線程的鎖,從而避免死鎖
- (void)NSRecursiveLockUpdate {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^recursiveBlock)(double count);
        recursiveBlock = ^(double count){
            [self->_recursive lock];
            //tryLock就不多介紹了,和Pthread的類似,注意返回值即可
            //[self->_recursive tryLock];
            if (count-- > 0) {
                self->_money++;
                recursiveBlock(count);
            }
            [self->_recursive unlock];
        };
        recursiveBlock(1000);
    });
}

synchronized

synchronized 同步鎖,即同步執(zhí)行,以此避免多線程同時(shí)操作同一塊代碼,基本上在各個(gè)平臺都會有其身影,雖然效率最低,但由于使用使用簡單,深得大家喜愛

實(shí)現(xiàn)如下所示

#pragma mark --同步鎖synchronized
- (void)synchronized {
    //使用簡單,直接對代碼塊加同步鎖,此代碼不會被多個(gè)線程直接執(zhí)行
    //可以間接理解為里面的任務(wù)被放到了一個(gè)同步隊(duì)列依次執(zhí)行(實(shí)際實(shí)現(xiàn)未知)
    @synchronized (self) {
        self->_money++;
    }
}

讀寫鎖

讀寫鎖 又被稱為 rw鎖或者 readwrite鎖,在 ios開發(fā)中雖能見到,但確不是最常用的(一般是數(shù)據(jù)庫操作才會用到)。

具體操作為:多讀單寫,即,寫入操作只能串行執(zhí)行,且寫入時(shí),不能讀取,而讀取需支持多線程操作,且讀取時(shí),不能寫入

相信大家也遇到過這樣的事,系統(tǒng)的屬性設(shè)置了 auto參數(shù),字面意思為原子性操作,其實(shí)際未能保證屬性字段的多線程安全(由于舊值的賦值未加鎖,同時(shí)寫入時(shí),會造成對象舊地址多次被release)

因此無論是想了解其實(shí)現(xiàn)方式,還是開發(fā)備用,都是有比較學(xué)習(xí)的

實(shí)現(xiàn)方式這里就提供兩種:pthread、GCD的barrier來實(shí)現(xiàn)

pthread讀寫鎖

使用前,需要先導(dǎo)入 pthread框架, 即 #import <pthread/pthread.h>

實(shí)現(xiàn)簡單,可以根據(jù)自己程序需要,選擇鎖初始化的合適位置

//初始化pthread讀寫鎖
- (void)setupPhreadRW {
    pthread_rwlock_init(&_lock, NULL);
    //使用完畢銷毀讀寫鎖
    //pthread_rwlock_destroy(&_lock);
}

#pragma mark --通過pthread讀寫鎖來設(shè)置
- (void)setLock1:(NSString *)lock1 {
    pthread_rwlock_wrlock(&_lock);
    _lock1 = lock1;
    pthread_rwlock_unlock(&_lock);

}
- (NSString *)lock1 {
    NSString *lock1 = nil;
    pthread_rwlock_rdlock(&_lock);
    lock1 = [_lock1 copy]; //copy到新的地址,避免解鎖后拿到舊值
    pthread_rwlock_unlock(&_lock);
    return lock1;
}

GCD的barrier讀寫鎖

GCD的barrier柵欄功能相信大家都聽說過,即在一個(gè)新創(chuàng)建的隊(duì)列中,barrier功能可以保證,在他之前的異步隊(duì)列執(zhí)行完畢才指定barrier中間的內(nèi)容,且還能保證barrier執(zhí)行完畢后,才之后barrier之后的任務(wù),且一個(gè)隊(duì)列可以有多個(gè)barrier

因此此特性可以用于完成一個(gè)讀寫鎖功能,即 barrier的代碼塊作為 寫入操作模塊

如下代碼所示,由于需要引入 新創(chuàng)建隊(duì)列,雖然使用起來不是不如pthread優(yōu)秀,但這種思想?yún)s可以再恰當(dāng)?shù)臅r(shí)候發(fā)芽出新樹苗

- (void)setupGCDRW {
    _queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT);
}

#pragma mark --通過GCD的barrier柵欄功能實(shí)現(xiàn)
//通過GCD的barrier柵欄功能實(shí)現(xiàn),缺點(diǎn)是需要借助自定義隊(duì)列實(shí)現(xiàn),且get方法無法重寫系統(tǒng)的,只能以回調(diào)的方式獲取值
//barrier功能使用global隊(duì)列會失效,全局隊(duì)列是無法阻塞的,里面有系統(tǒng)的一些任務(wù)執(zhí)行
- (void)setLock2:(NSString *)lock2 {
    dispatch_barrier_async(_queue, ^{
        self->_lock2 = lock2;
    });
}
- (void)getLock2WithBlock:(void(^)(NSString *))block {
    dispatch_async(_queue, ^{
        block(self->_lock2);
    });
}

最后

相信看了這篇文章能給大家?guī)砀嗍肇?/p>

最后,你能根據(jù)讀寫鎖的特性,利用現(xiàn)有的鎖,再寫出一個(gè)完整的讀寫鎖功能出來么!

?著作權(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í)候多個(gè)線程可能會訪問同一塊資源,這樣就很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全等問題。我們常常會使用一些...
    遠(yuǎn)方竹葉閱讀 1,290評論 0 8
  • iOS開發(fā)中常用的幾種鎖 簡介: 操作系統(tǒng)在進(jìn)行多線程調(diào)度的時(shí)候,為了保證多線程安全引入了鎖的機(jī)制,以實(shí)現(xiàn)指定代碼...
    sunnyxg0812閱讀 1,377評論 0 2
  • 本文節(jié)選自成長手冊 文章推薦和參考深入理解 iOS 開發(fā)中的鎖pthread的各種同步機(jī)制 多線程編程被普遍認(rèn)為復(fù)...
    百草紀(jì)閱讀 2,922評論 1 9
  • 轉(zhuǎn)鏈接:https://juejin.im/post/5d395318f265da1b8608ca98 自旋鎖 O...
    DL是誰閱讀 551評論 0 0
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,916評論 2 7

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