iOS 中幾種常用的鎖總結(jié)

多線程編程中,應(yīng)該盡量避免資源在線程之間共享,以減少線程間的相互作用。 但是總是有多個(gè)線程相互干擾的情況(如多個(gè)線程訪問一個(gè)資源)。在線程必須交互的情況下,就需要一些同步工具,來確保當(dāng)它們交互的時(shí)候是安全的。

鎖是線程編程同步工具的基礎(chǔ)。iOS開發(fā)中常用的鎖有如下幾種:

  1. @synchronized
  2. NSLock 對(duì)象鎖
  3. NSRecursiveLock 遞歸鎖
  4. NSConditionLock 條件鎖
  5. pthread_mutex 互斥鎖(C語言)
  6. dispatch_semaphore 信號(hào)量實(shí)現(xiàn)加鎖(GCD)
  7. OSSpinLock (暫不建議使用,原因參見這里

下圖是它們的性能對(duì)比:

性能表 圖1.1
  • ** @synchronized 關(guān)鍵字加鎖 互斥鎖,性能較差不推薦使用**
 @synchronized(這里添加一個(gè)OC對(duì)象,一般使用self) {
       這里寫要加鎖的代碼
  }
 注意點(diǎn)
   1.加鎖的代碼盡量少
   2.添加的OC對(duì)象必須在多個(gè)線程中都是同一對(duì)象
    3.優(yōu)點(diǎn)是不需要顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制。
    4. @synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對(duì)象。

下面通過 賣票的例子 展示使用

    //設(shè)置票的數(shù)量為5
    _tickets = 5;
    
    //線程1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
    
    //線程2
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });

- (void)saleTickets
{
    while (1) {
        @synchronized(self) {
            [NSThread sleepForTimeInterval:1];
            if (_tickets > 0) {
                _tickets--;
                NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
            } else {
                NSLog(@"票賣完了  Thread:%@",[NSThread currentThread]);
                break;
            }
        }
    }
}
控制臺(tái)打印
  • ** NSLock 互斥鎖 不能多次調(diào)用 lock方法,會(huì)造成死鎖**

在Cocoa程序中NSLock中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的互斥鎖。
所有鎖(包括NSLock)的接口實(shí)際上都是通過NSLocking協(xié)議定義的,它定義了lockunlock方法。你使用這些方法來獲取和釋放該鎖。

NSLock類還增加了tryLocklockBeforeDate:方法。
tryLock試圖獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候,它不會(huì)阻塞線程,相反,它只是返回NO。
lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒有在規(guī)定的時(shí)間內(nèi)被獲得,它會(huì)讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。

還是賣票的例子

    //設(shè)置票的數(shù)量為5
    _tickets = 5;
    
    //創(chuàng)建鎖
    _mutexLock = [[NSLock alloc] init];
    
    //線程1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
    
    //線程2
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });

- (void)saleTickets
{

    while (1) {
        [NSThread sleepForTimeInterval:1];
        //加鎖
        [_mutexLock lock];
        if (_tickets > 0) {
            _tickets--;
            NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);        
        } else {
            NSLog(@"票賣完了  Thread:%@",[NSThread currentThread]);
            break;
        }
        //解鎖
        [_mutexLock unlock];
    }
}
控制臺(tái)打印
  • ** NSRecursiveLock 遞歸鎖**

使用鎖最容易犯的一個(gè)錯(cuò)誤就是在遞歸或循環(huán)中造成死鎖
如下代碼中,因?yàn)樵诰€程1中的遞歸block中,鎖會(huì)被多次的lock,所以自己也被阻塞了

    //創(chuàng)建鎖
    _mutexLock = [[NSLock alloc]init];
  
    //線程1
    dispatch_async(self.concurrentQueue, ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_mutexLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                TestMethod(value--);
            }
            [_mutexLock unlock];
        };
        
        TestMethod(5);
    });
    
  

此處將NSLock換成NSRecursiveLock,便可解決問題。
NSRecursiveLock類定義的鎖可以在同一線程多次lock,而不會(huì)造成死鎖。
遞歸鎖會(huì)跟蹤它被多少次lock。每次成功的lock都必須平衡調(diào)用unlock操作。
只有所有的鎖住和解鎖操作都平衡的時(shí)候,鎖才真正被釋放給其他線程獲得。

    //創(chuàng)建鎖
    _rsLock = [[NSRecursiveLock alloc] init];
    
   //線程1
    dispatch_async(self.concurrentQueue, ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_rsLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                TestMethod(value--);
            }
            [_rsLock unlock];
        };
        
        TestMethod(5);
    });
  • ** NSConditionLock 條件鎖 **

直接看代碼和介紹

  //主線程中
    NSConditionLock *theLock = [[NSConditionLock alloc] init];
    
    //線程1
    dispatch_async(self.concurrentQueue, ^{
        for (int i=0;i<=3;i++)
        {
            [theLock lock];
            NSLog(@"thread1:%d",i);
            sleep(1);
            [theLock unlockWithCondition:i];
        }
    });
    
    //線程2
    dispatch_async(self.concurrentQueue, ^{
        [theLock lockWhenCondition:2];
        NSLog(@"thread2");
        [theLock unlock];
    });
控制臺(tái)打印

在線程1中的加鎖使用了lock,是不需要條件的,所以順利的就鎖住了。
unlockWithCondition:在開鎖的同時(shí)設(shè)置了一個(gè)整型的條件 2 。
線程2則需要一把被標(biāo)識(shí)為2的鑰匙,所以當(dāng)線程1循環(huán)到 i = 2 時(shí),線程2的任務(wù)才執(zhí)行。

NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對(duì)應(yīng)的,只是lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,當(dāng)然這是與你的需求相關(guān)的。

  • pthread_mutex 互斥鎖
 __block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    
    //線程1
    dispatch_async(self.concurrentQueue), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"任務(wù)1");
        sleep(2);
        pthread_mutex_unlock(&mutex);
    });
    
    //線程2
    dispatch_async(self.concurrentQueue), ^{
        sleep(1);
        pthread_mutex_lock(&mutex);
        NSLog(@"任務(wù)2");
        pthread_mutex_unlock(&mutex);
    });
  • dispatch_semaphore 信號(hào)量實(shí)現(xiàn)加鎖
    GCD中也已經(jīng)提供了一種信號(hào)機(jī)制,使用它我們也可以來構(gòu)建一把”鎖”(從本質(zhì)意義上講,信號(hào)量與鎖是有區(qū)別,請(qǐng)看互斥鎖與信號(hào)量的作用與區(qū)別):
   // 創(chuàng)建信號(hào)量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
         NSLog(@"任務(wù)1");
        sleep(10);
        dispatch_semaphore_signal(semaphore);
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任務(wù)2");
        dispatch_semaphore_signal(semaphore);
    });
  • OSSpinLock

OSSpinLock 在圖1.1 中顯示的效率最高(暫不建議使用,原因參見這里

  //設(shè)置票的數(shù)量為5
    _tickets = 5;
    //創(chuàng)建鎖
    _pinLock = OS_SPINLOCK_INIT;
    //線程1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
    //線程2
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });

- (void)saleTickets {
    
        while (1) {
            [NSThread sleepForTimeInterval:1];
            //加鎖
            OSSpinLockLock(&_pinLock);
            
            if (_tickets > 0) {
                _tickets--;
                NSLog(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
                
            } else {
                NSLog(@"票賣完了  Thread:%@",[NSThread currentThread]);
                break;
            }
            //解鎖
            OSSpinLockUnlock(&_pinLock);
        }

}
控制臺(tái)輸出
最后編輯于
?著作權(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)容

  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對(duì)資源訪問的限制iOS中常見鎖的性能對(duì)比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,625評(píng)論 0 6
  • iOS線程安全的鎖與性能對(duì)比 一、鎖的基本使用方法 1.1、@synchronized 這是我們最熟悉的枷鎖方式,...
    Jacky_Yang閱讀 2,371評(píng)論 0 17
  • 2016年國慶假期終于把此書過完,整理筆記和體會(huì)于此。 關(guān)于書名 書名源于俄羅斯的演員斯坦尼斯拉夫斯基創(chuàng)作的《演員...
    李劍飛的簡(jiǎn)書閱讀 7,446評(píng)論 2 65
  • 在平時(shí)的開發(fā)中經(jīng)常使用到多線程,在使用多線程的過程中,難免會(huì)遇到資源競(jìng)爭(zhēng)的問題,那我們?cè)趺磥肀苊獬霈F(xiàn)這種問題那? ...
    IAMCJ閱讀 3,315評(píng)論 2 25
  • 最怕的就是沒有自由時(shí)候的無助感 這個(gè)世界并不是我們想象的那個(gè)樣子。以前一直以為如果有人在我的屋子里欺負(fù)我,我是可以...
    復(fù)蘇森林閱讀 116評(píng)論 0 1

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