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

多線程編程中庭砍,應該盡量避免資源在線程之間共享渐裂,以減少線程間的相互作用阳液。 但是總是有多個線程相互干擾的情況(如多個線程訪問一個資源)繁疤。在線程必須交互的情況下咖为,就需要一些同步工具,來確保當它們交互的時候是安全的稠腊。

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

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

下圖是它們的性能對比:

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

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

    //設置票的數(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;
            }
        }
    }
}
控制臺打印
  • ** NSLock 互斥鎖 不能多次調(diào)用 lock方法,會造成死鎖**

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

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

還是賣票的例子

    //設置票的數(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];
    }
}
控制臺打印
  • ** NSRecursiveLock 遞歸鎖**

使用鎖最容易犯的一個錯誤就是在遞歸或循環(huán)中造成死鎖
如下代碼中鹤竭,因為在線程1中的遞歸block中踊餐,鎖會被多次的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,而不會造成死鎖。
遞歸鎖會跟蹤它被多少次lock窜管。每次成功的lock都必須平衡調(diào)用unlock操作散劫。
只有所有的鎖住和解鎖操作都平衡的時候,鎖才真正被釋放給其他線程獲得幕帆。

    //創(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];
    });
控制臺打印

在線程1中的加鎖使用了lock获搏,是不需要條件的,所以順利的就鎖住了失乾。
unlockWithCondition:在開鎖的同時設置了一個整型的條件 2 常熙。
線程2則需要一把被標識為2的鑰匙,所以當線程1循環(huán)到 i = 2 時碱茁,線程2的任務才執(zhí)行裸卫。

NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對應的纽竣,只是lock,lockWhenCondition:與unlock墓贿,unlockWithCondition:是可以隨意組合的,當然這是與你的需求相關(guān)的退个。

  • pthread_mutex 互斥鎖
 __block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    
    //線程1
    dispatch_async(self.concurrentQueue), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"任務1");
        sleep(2);
        pthread_mutex_unlock(&mutex);
    });
    
    //線程2
    dispatch_async(self.concurrentQueue), ^{
        sleep(1);
        pthread_mutex_lock(&mutex);
        NSLog(@"任務2");
        pthread_mutex_unlock(&mutex);
    });
  • dispatch_semaphore 信號量實現(xiàn)加鎖
    GCD中也已經(jīng)提供了一種信號機制募壕,使用它我們也可以來構(gòu)建一把”鎖”(從本質(zhì)意義上講,信號量與鎖是有區(qū)別语盈,請看互斥鎖與信號量的作用與區(qū)別):
   // 創(chuàng)建信號量
    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(@"任務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(@"任務2");
        dispatch_semaphore_signal(semaphore);
    });
  • OSSpinLock

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

  //設置票的數(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);
        }

}
控制臺輸出
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刀荒,隨后出現(xiàn)的幾起案子代嗤,更是在濱河造成了極大的恐慌,老刑警劉巖缠借,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件干毅,死亡現(xiàn)場離奇詭異,居然都是意外死亡泼返,警方通過查閱死者的電腦和手機硝逢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绅喉,“玉大人渠鸽,你說我怎么就攤上這事妹笆∽つ牛” “怎么了冗懦?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵搂擦,是天一觀的道長。 經(jīng)常有香客問我剑逃,道長宽涌,這世上最難降的妖魔是什么褐鸥? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮那婉,結(jié)果婚禮上板甘,老公的妹妹穿的比我還像新娘。我一直安慰自己详炬,他們只是感情好虾啦,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痕寓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝇闭。 梳的紋絲不亂的頭發(fā)上呻率,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音呻引,去河邊找鬼礼仗。 笑死,一個胖子當著我的面吹牛逻悠,可吹牛的內(nèi)容都是我干的元践。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼童谒,長吁一口氣:“原來是場噩夢啊……” “哼单旁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饥伊,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤象浑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后琅豆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愉豺,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年茫因,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚪拦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡冻押,死狀恐怖驰贷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翼雀,我是刑警寧澤饱苟,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站狼渊,受9級特大地震影響箱熬,放射性物質(zhì)發(fā)生泄漏类垦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一城须、第九天 我趴在偏房一處隱蔽的房頂上張望蚤认。 院中可真熱鬧,春花似錦糕伐、人聲如沸砰琢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陪汽。三九已至,卻和暖如春褥蚯,著一層夾襖步出監(jiān)牢的瞬間挚冤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工赞庶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留训挡,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓歧强,卻偏偏與公主長得像澜薄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摊册,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容