iOS中的鎖——由屬性atomic想到的線程安全

本文不介紹各種鎖的高級(jí)用法,只是整理鎖相關(guān)的知識(shí)點(diǎn),幫助理解。

鎖的作用

防止在多線程(多任務(wù))的情況下對(duì)共享資源(臨界資源)的臟讀或者臟寫。

自旋鎖和互斥鎖

共同點(diǎn):都能保證同一時(shí)刻只能有一個(gè)線程操作鎖住的代碼。都能保證線程安全。
不同點(diǎn)

  • 互斥鎖(mutex):當(dāng)上一個(gè)線程的任務(wù)沒有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)進(jìn)入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢(sleep-waiting),當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢,下一個(gè)線程會(huì)自動(dòng)喚醒然后執(zhí)行任務(wù)。
  • 自旋鎖(Spin lock):當(dāng)上一個(gè)線程的任務(wù)沒有執(zhí)行完畢的時(shí)候(被鎖住),那么下一個(gè)線程會(huì)一直等待(busy-waiting),當(dāng)上一個(gè)線程的任務(wù)執(zhí)行完畢,下一個(gè)線程會(huì)立即執(zhí)行。
  • 由于自旋鎖不會(huì)引起調(diào)用者睡眠,所以自旋鎖的效率遠(yuǎn)高于互斥鎖
  • 自旋鎖會(huì)一直占用CPU,也可能會(huì)造成死鎖

自旋鎖有bug! ibireme大神的文章《不再安全的 OSSpinLock》
不同優(yōu)先級(jí)線程調(diào)度算法會(huì)有優(yōu)先級(jí)反轉(zhuǎn)問題,比如低優(yōu)先級(jí)獲鎖訪問資源,高優(yōu)先級(jí)嘗試訪問時(shí)會(huì)等待,這時(shí)低優(yōu)先級(jí)又沒法爭過高優(yōu)先級(jí)導(dǎo)致任務(wù)無法完成lock釋放不了。

1. 原子操作

  • nonatomic:非原子屬性,非線程安全,適合小內(nèi)存移動(dòng)設(shè)備
  • atomic:原子屬性,default,線程安全(內(nèi)部使用自旋鎖),消耗大量資源
    • 單寫多讀,只為setter方法加鎖,不影響getter

    • 相關(guān)代碼如下:

      static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 
      {
         if (offset == 0) {
             object_setClass(self, newValue);
             return;
         }
      
         id oldValue;
         id *slot = (id*) ((char*)self + offset);
      
         if (copy) {
             newValue = [newValue copyWithZone:nil];
         } else if (mutableCopy) {
             newValue = [newValue mutableCopyWithZone:nil];
         } else {
             if (*slot == newValue) return;
             newValue = objc_retain(newValue);
         }
      
         if (!atomic) {
             oldValue = *slot;
             *slot = newValue;
         } else {
             spinlock_t& slotlock = PropertyLocks[slot];
             slotlock.lock();
             oldValue = *slot;
             *slot = newValue;        
             slotlock.unlock();
         }
      
         objc_release(oldValue);
      }
      
      void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
      {
         bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
         bool mutableCopy = (shouldCopy == MUTABLE_COPY);
         reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
      }
      

很容易理解的代碼,可變拷貝和不可變拷貝會(huì)開辟新的空間,兩者皆不是則持有(引用計(jì)數(shù)+1),相比nonatomic只是多了一步鎖操作。

2. synchronized 條件鎖

使用最簡單,性能也最差。

@synchronized(obj) {
    // 內(nèi)部會(huì)添加異常處理,所以耗時(shí)
    NSLog(@"自動(dòng)加鎖,自動(dòng)解鎖,自動(dòng)銷毀");
}

obj為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥

3. dispatch_semaphore 信號(hào)量

用于線程同步,有序訪問。不支持遞歸。

  • 創(chuàng)建信號(hào):dispatch_semaphore_create(long value) 傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout,時(shí)間到后會(huì)執(zhí)行其后的語句
  • 等待信號(hào)到達(dá):dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 使得signal-1
  • 發(fā)送信號(hào),解除等待狀態(tài):dispatch_semaphore_signal(dispatch_semaphore_t deem)使得signal+1

這里順便寫一下dispatch_barrier,一個(gè)dispatch_barrier允許在一個(gè)并發(fā)隊(duì)列(如果是串行隊(duì)列或全局隊(duì)列相當(dāng)于dispatch_(a)sync)中創(chuàng)建一個(gè)同步點(diǎn)。當(dāng)在并發(fā)隊(duì)列中遇到一個(gè)barrier,他會(huì)延遲執(zhí)行barrier的block,等待所有在barrier之前提交的blocks執(zhí)行結(jié)束。 這時(shí),barrier block自己開始執(zhí)行。 之后, 隊(duì)列繼續(xù)正常的執(zhí)行操作。
dispatch_barrier_asyncdispatch_barrier_sync的區(qū)別在于是否會(huì)等待自己的block完成再執(zhí)行后面的任務(wù)。

一個(gè)同步訪問網(wǎng)絡(luò)的例子
- (void)syncRequestWithUrl:(NSURL*)url {
    NSMutableURLRequest *req = [[NSMutableURLRequest alloc]initWithURL:url];
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
    __block NSURLSessionTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error && data) {

        } else {
            NSLog(@"error: %@",[error description]);
        }
        dispatch_semaphore_signal(semaphore);
    }];
    [dataTask resume];
        
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

4. pthread_mutex 互斥鎖

Facebook的 KVOController 有使用到。(先使用的是OSSpinLock,由于自旋鎖的優(yōu)先級(jí)反轉(zhuǎn)問題后改用pthread_mutex)

使用需導(dǎo)入頭文件:
#import <pthread.h>

  • 聲明:pthread_mutex_t _mutex;
  • 初始化:pthread_mutex_init(&_mutex, NULL);
  • 加鎖:pthread_mutex_lock(&_mutex);
  • 解鎖:pthread_mutex_unlock(&_mutex);
  • 銷毀:pthread_mutex_destroy(&_mutex);

5. pthread_mutex(recursive) 遞歸鎖

遞歸鎖允許同一個(gè)線程在未釋放其擁有的鎖時(shí)反復(fù)對(duì)該鎖進(jìn)行加鎖操作。不會(huì)造成死鎖。

static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認(rèn)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設(shè)置鎖類型,這邊是設(shè)置為遞歸鎖
pthread_mutex_init(&pLock, &attr); // 初始化的時(shí)候帶入?yún)?shù)
pthread_mutexattr_destroy(&attr); //銷毀一個(gè)屬性對(duì)象,在重新進(jìn)行初始化之前該結(jié)構(gòu)不能重新使用

6. OSSpinLock 自旋鎖

是性能最佳的鎖,但由于線程調(diào)度算法(高優(yōu)先級(jí)線程始終會(huì)在低優(yōu)先級(jí)線程前執(zhí)行,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)線程的干擾)優(yōu)先級(jí)反轉(zhuǎn)的原因逐漸被其他鎖取代。不支持遞歸。

使用需導(dǎo)入頭文件
#import <libkern/OSAtomic.h>

  • OSSpinLock oslock = OS_SPINLOCK_INIT;:默認(rèn)值為 0,在 locked 狀態(tài)時(shí)就會(huì)大于 0,unlocked狀態(tài)下為 0
  • OSSpinLockLock(&oslock);:上鎖
  • OSSpinLockUnlock(&oslock);:解鎖
  • OSSpinLockTry(&oslock):嘗試加鎖,可以加鎖則立即加鎖并返回YES,反之返回NO

提一下os_unfair_lock,蘋果解決優(yōu)先級(jí)反轉(zhuǎn)的問題整出的。。
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);

7. NSLock

Cocoa提供的最基本的鎖對(duì)象,實(shí)際是在內(nèi)部封裝了pthread_mutex。對(duì)象鎖均實(shí)現(xiàn)了NSLocking協(xié)議。

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end
  • trylock:能加鎖返回YES并執(zhí)行加鎖操作,相當(dāng)于lock,反之返回NO
  • lockBeforeDate:表示會(huì)在傳入的時(shí)間內(nèi)嘗試加鎖,若能加鎖則執(zhí)行加鎖操作并返回YES,反之返回NO

8. NSRecursiveLock 遞歸鎖

NSLock的遞歸版本,解決在循環(huán)或遞歸時(shí)造成的死鎖問題。使用同上。

9. NSCondition

最基本的條件鎖,底層是通過條件變量(condition variable) pthread_cond_t 來實(shí)現(xiàn),實(shí)際上封裝了一個(gè)互斥鎖和條件變量。手動(dòng)控制線程waitsignal

  • wait:進(jìn)入等待狀態(tài)
  • waitUntilDate::讓一個(gè)線程等待一定的時(shí)間
  • signal:喚醒一個(gè)等待的線程
  • broadcast:喚醒所有等待的線程

10. NSConditionLock 條件鎖

API名說明一切...

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

性能相對(duì)較差,但使用NSConditionLock可以處理任務(wù)間的依賴關(guān)系。

Additional

11. pthread_rwlock 讀寫鎖

讀寫鎖是用來解決文件讀寫問題的,讀操作可以共享,寫操作是排他的,讀可以有多個(gè)在讀,寫只有唯一個(gè)在寫,同時(shí)寫的時(shí)候不允許讀。

  • 當(dāng)讀寫鎖被一個(gè)線程以讀模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程還可以繼續(xù)進(jìn)行
  • 當(dāng)讀寫鎖被一個(gè)線程以寫模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程也被阻塞
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 讀模式
pthread_rwlock_wrlock(&lock);
// 寫模式
pthread_rwlock_rdlock(&lock);
// 讀模式或者寫模式的解鎖
pthread_rwlock_unlock(&lock);

對(duì)于讀數(shù)據(jù)比修改數(shù)據(jù)頻繁的應(yīng)用,用讀寫鎖代替互斥鎖可以提高效率。因?yàn)槭褂没コ怄i時(shí),即使是讀出數(shù)據(jù)(相當(dāng)于操作臨界區(qū)資源)都要上互斥鎖,而采用讀寫鎖,則可以在任一時(shí)刻允許多個(gè)讀出者存在,提高了更高的并發(fā)度,同時(shí)在某個(gè)寫入者修改數(shù)據(jù)期間保護(hù)該數(shù)據(jù),以免任何其它讀出者或?qū)懭胝叩母蓴_。

12. NSDistributedLock 分布式鎖

Mac開發(fā)使用,mark

引用

更詳細(xì)的資料參考:

iOS 開發(fā)中的八種鎖
iOS中保證線程安全的幾種方式與性能對(duì)比
深入理解iOS開發(fā)中的鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對(duì)資源訪問的限制iOS中常見鎖的性能對(duì)比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,553評(píng)論 0 6
  • 線程安全是怎么產(chǎn)生的 常見比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量,這個(gè)時(shí)候一定要考慮線程安全和同步。 - (v...
    幽城88閱讀 689評(píng)論 0 0
  • iOS線程安全的鎖與性能對(duì)比 一、鎖的基本使用方法 1.1、@synchronized 這是我們最熟悉的枷鎖方式,...
    Jacky_Yang閱讀 2,263評(píng)論 0 17
  • demo下載 建議一邊看文章,一邊看代碼。 聲明:關(guān)于性能的分析是基于我的測(cè)試代碼來的,我也看到和網(wǎng)上很多測(cè)試結(jié)果...
    炸街程序猿閱讀 807評(píng)論 0 2
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝,對(duì)鎖的使用很少,剛好之前面試中被問到的關(guān)于并發(fā)編程鎖的問題,都是一知...
    喵渣渣閱讀 3,730評(píng)論 0 33