iOS: 線程中那些常見的鎖

1、介紹ios

在多線程開發中,鎖的使用基本必不可少,主要是爲了解決資源共享時出現爭奪而致使數據不一致的問題,也就是線程安全問題。鎖的種類不少,在實際開發中,須要根據狀況選擇性的選取使用,畢竟使用鎖也是消耗CPU的。 本人雖然一直有使用多線程進行開發,可是對於鎖的使用和理解並非特別的深刻,這不看到一篇挺mark的博客:https://www.jianshu.com/p/a236130bf7a2,在此基礎上稍添加點東西轉載過來(尊重原創),一是爲了記錄便於隨時翻閱,而是爲了寫一遍加深印象,知識都是一個copy和attract的過程。 編程

 

2、種類安全

一、互斥鎖多線程

概念:對共享數據進行鎖定,保證同一時刻只能有一個線程去操做。 併發

  • 搶到鎖的線程先執行,沒有搶到鎖的線程就會被掛起等待。
  • 等鎖用完後須要釋放,而後其它等待的線程再去搶這個鎖,那個線程搶到就讓那個線程再執行。
  • 具體哪一個線程搶到這個鎖是由cpu調度決定的。

經常使用:異步

@synchronized:同步代碼塊async

example:執行操做分佈式

/**
 *設置屬性值
 */
-(void)setMyTestString:(NSString *)myTestString{
    @synchronized(self) {
        // todo something
        _myTestString = myTestString;
    }
}

example:建立單例函數

//注意:此時爲了保證單例模式的更加嚴謹,須要重寫`allocWithZone`方法,保證其餘開發者使用`alloc`和`init`方法時,再也不建立新的對象。必要的時候還須要重寫`copyWithZone`方法防止`copy`屬性對單例模式的影響。 iOS中還有一種更加輕便的方法實現單例模式,即便用GCD中的dispatch_once函數實現。
+(instancetype)shareInstance{ // 1.定義一個靜態實例,初值nil static TestSynchronized *myClass = nil; // 2.添加同步鎖,建立實例 @synchronized(self) { // 3.判斷實例是否建立過,建立過則退出同步鎖,直接返回該實例 if (!myClass) { // 4.未建立過,則新建一個實例並返回 myClass = [[self alloc] init]; } } return myClass; }

NSLock:不能迭代加鎖,若是發生兩次lock,而未unlock過,則會產生死鎖問題。工具

example:執行操做

///定義一個靜態鎖變量, lock--unlock 、tryLuck---unLock  必須成對存在
static NSLock *mylock;
-(void)viewDidLoad {
    [super viewDidLoad];
    mylock = [[NSLock alloc] init];
}

//當前線程鎖失敗,也能夠繼續其它任務,用 trylock 合適
-(void)myLockTest1{
    if ([mylock tryLock]) {
        // to do something
        [mylock unlock];
    }
}

//當前線程只有鎖成功後,纔會作一些有意義的工做,那就lock,不必輪詢trylock
-(void)myLockTest2{
    [mylock lock];
    // to do something
    [mylock unlock];
}

 

二、遞歸鎖

概念:遞歸鎖能夠被同一線程屢次請求,而不會引發死鎖,即在屢次被同一個線程進行加鎖時,不會形成死鎖,這主要是用在循環或遞歸操做中。

  • 遞歸鎖會跟蹤它被lock的次數,每次成功的lock都必須平衡調用unlock操做。
  • 只有全部達到這種平衡,鎖最後才能被釋放,以供其它線程使用。

經常使用:

NSRecursiveLock: 遞歸鎖

example: 異步執行block

//建立遞歸鎖
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^MyRecursiveLockBlock)(int value);
        MyRecursiveLockBlk = ^(int value){
            [myRecursiveLock lock];
            if (value > 0) {
                // to do something
                NSLog(@"MyRecursiveLockBlk value = %d", value);
                MyRecursiveLockBlock(value - 1);
            }
            [myRecursiveLock unlock];
        };
        MyRecursiveLockBlock(6);
});

//注意:此時若是將例程中的遞歸鎖換成互斥鎖:
//NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];換成
//NSLock *myLock = [[NSLock alloc] init];,則會發生死鎖問題。

 

三、讀寫鎖

概念:讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分紅讀者和寫者,讀者只對共享資源進行讀訪問,寫者則須要對共享資源進行寫操做。

  • 讀寫鎖將訪問者分爲讀出寫入兩種,當讀寫鎖在讀加鎖模式下,全部以讀加鎖方式訪問該資源時,都會得到訪問權限,而全部試圖以寫加鎖方式對其加鎖的線程都將阻塞,直到全部的讀鎖釋放。

  • 當在寫加鎖模式下,全部試圖對其加鎖的線程都將阻塞。

經常使用:

pthread_rwlock_t(讀寫鎖)、 pthread_rwlock_wrlock(寫鎖)、 pthread_rwlock_rdlock(讀鎖)

example: 異步讀寫數據

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property(nonatomic, copy) NSString *rwStr;
@end

@implementation ViewController

///全局的讀寫鎖
pthread_rwlock_t rwlock;

-(void)viewDidLoad {
    [super viewDidLoad];
    // 初始化讀寫鎖
    pthread_rwlock_init(&rwlock,NULL);
    __block int i;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            NSString *temp = [NSString stringWithFormat:@"writing == %d", i];
            [self writingLock:temp];
            i--;
        }  
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            [self readingLock];
            i--;
        }
    });
}
// 寫加鎖
-(void)writingLock:(NSString *)temp{
    pthread_rwlock_wrlock(&rwlock);
    // writing
    self.rwStr = temp;
    NSLog(@"%@", temp);
    pthread_rwlock_unlock(&rwlock);
}
// 讀加鎖
-(NSString *)readingLock{
    pthread_rwlock_rdlock(&rwlock);
    // reading
    NSString *str = self.rwStr;
    NSLog(@"reading == %@",self.rwStr);
    pthread_rwlock_unlock(&rwlock);
    return str;
}
@end

  

四、自旋鎖

概念:它是一種忙等的鎖,適用於輕量訪問。自旋鎖是非阻塞的,當一個線程沒法獲取自旋鎖時,會自旋,直到該鎖被釋放,等待的過程當中線程並不會掛起。(實質上就是,若是自旋鎖已經被別的執行單元保持,調用者就一直循環在等待該自旋鎖的保持着已經釋放了鎖)。

  • 自旋鎖的使用者通常保持鎖的時間很短,此時其效率遠高於互斥鎖

  • 自旋鎖保持期間是搶佔失效的

  • 優勢:不用進行線程的切換    
  • 缺點:若是一個線程霸佔鎖的時間過長,自旋會消耗CPU資源

經常使用:

OSSpinLock:自旋鎖

example: 執行操做

// 頭文件
#import <libkern/OSAtomic.h>
// 初始化自旋鎖
static OSSpinLock myLock = OS_SPINLOCK_INIT;
// 自旋鎖的使用
-(void)SpinLockTest{
    OSSpinLockLock(&myLock);
    // to do something
    OSSpinLockUnlock(&myLock);
}
 

 

五、分佈鎖

概念:跨進程的分佈式鎖是進程間同步的工具,底層是用文件系統實現的互斥鎖,並不強制進程休眠,而是起到告知的做用。

  • 它沒有實現NSLocking協議,因此沒有會阻塞線程的lock方法,取而代之的是非阻塞的tryLock方法來獲取鎖,用unLock方法釋放鎖。
  • 若是一個獲取鎖的進程在釋放鎖以前就退出了,那麼鎖就一直不能釋放,此時能夠經過breakLock強行獲取鎖。

經常使用:

NSDistributedLock:自旋鎖

example: 執行操做

//給文件建立分佈鎖
NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
while (![lock tryLock])
{
    sleep(1);
}

//do something

[lock unlock];

//但在實際使用過程當中,當執行到do something時程序退出,程序再次啓動以後tryLock就不再能成功了,陷入死鎖狀態.這是使用NSDistributedLock時很是隱蔽的風險.其//實要解決的問題就是如何在進程退出時會自動釋放鎖.

 

六、條件變量

概念:與互斥鎖不一樣,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊狀況發生爲止。一般條件變量和互斥鎖同時使用。

  • 一個線程須要等待某一條件出現才能繼續執行,而這個條件是由別的線程產生的,這個時候就用到條件變量。常見的狀況是:生產者-消費者問題。

  • 條件變量可讓一個線程等待某一條件,當條件知足時,會收到通知。在獲取條件變量並等待條件發生的過程當中,也會產生多線程的競爭,因此條件變量一般和互斥鎖一塊兒工做。 

經常使用:

NSCondition:是互斥鎖和條件鎖的結合,即一個線程在等待signal而阻塞時,能夠被另外一個線程喚醒,因爲操做系統實現的差別,即便沒有發送signal消息,線程也有可能被喚醒,因此須要增長謂詞變量來保證程序的正確性。

example: 執行操做

// 建立鎖
NSCondition *condition = [[NSCondition alloc] init];
static int count = 0;
// 生產者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    while(count<20)
    {
       [condition lock];
       // 生產
       count ++;
       NSLog(@"生產 = %d",count);
       [condition signal];
       [condition unlock];
    }
});
// 消費者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    while (count>0)
    {
        [condition lock];
        // 消耗
        count --;
        NSLog(@"消耗剩餘 = %d",count);
        [condition unlock];
    }
});

NSConditionLock:與NSCondition的實現機制不同,當定義的條件成立的時候會獲取鎖,反之,釋放鎖。

example: 執行操做

// 建立鎖
NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT];
static int count = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 生產者
    while(true)
    {
         [condLock lock];
         // 生產
         count ++;
         [condLock unlockWithCondition:ConditionHAS];
    }
}

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 消費者
    while (true)
    {
         [condLock lockWhenCondition:ConditionHAS];
         // 消耗
         count --;
         [condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)];
    }
}

  

七、信號量

概念:信號量是一個計數器,經常使用於處理進程或線程的同步問題,特別是對臨界資源的同步訪問。臨界資源能夠簡單的理解爲在某一時刻只能由一個進程或線程進行操做的資源,這裏的資源能夠是一段代碼、一個變量或某種硬件資源。

  • 信號量:能夠是一種特殊的互斥鎖,能夠是資源的計數器
  • 可使用GCD中的Dispatch Semaphore實現,Dispatch Semaphore是持有計數的信號,該計數是多線程編程中的計數類型信號。計數爲0時等待,計數大於等於1時,減1爲不等待。

經常使用: 

dispatch_semaphore_t(信號)、dispatch_semaphore_signal(持有信號)、diapatch_semaphore_wait(釋放信號)

example: 執行操做

//建立信號
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[self getTasksWithCompletionHandler:^ {

    //doing something

    //持有信號
     dispatch_semaphore_signal(semaphore);
}];

//釋放信號
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

 

八、柵欄/屏障(barrier)

概念:dispatch_barrier_async函數的做用與barrier的意思相同,在進程管理中起到一個柵欄的做用,它等待全部位於barrier函數以前的操做執行完畢後執行,而且在barrier函數執行以後,barrier函數以後的操做纔會獲得執行,該函數須要同dispatch_queue_create函數生成的concurrent Dispatch Queue隊列一塊兒使用。

  • 柵欄必須單獨執行,不能與其餘任務併發執行,柵欄只對併發隊列有意義。
  • 柵欄只有等待當前隊列全部併發任務都執行完畢後,纔會單獨執行,帶起執行完畢,再按照正常的方式繼續向下執行。

經常使用:

dispatch_barrier_async:異步柵欄函數

example: 多讀單寫(讀讀併發、讀寫互斥、寫寫互斥)

- (id)objectForKey:(NSString *)key {
    
     __block id obj;
     //同步讀取指定數據  
     dispatch_sync(concurrent_queue, ^{
          obj = [userCenterDic objectForKey:key];      
     });

     return obj;
}    


-(void)setObject:(id )obj foeKey:(NSString *)key{
      
     //異步柵欄調用設置數據
     dispatch_async(concurrent_queue, ^{
           [userCenterDic setObject:obj forKey:key];      
     });
}

 

九、pthread_mutex

概念:C語言定義下的多線程加鎖方式,在不少OC對象的底層結構中,能夠看到pthread_mutex使用的仍是很受蘋果官方推薦的。

用法:

  • pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr); 初始化鎖變量mutex,attr爲鎖屬性,NULL值爲默認屬性。
  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 宏初始化鎖變量mutex

  • pthread_mutexattr_settype(pthread_mutexattr_t attr,  int type); 設置鎖類型
  • pthread_mutex_lock(pthread_mutex_t* mutex);加鎖
  • pthread_mutex_tylock(pthread_mutex_t* mutex);加鎖,可是與2不同的是當鎖已經在使用的時候,返回爲EBUSY,而不是掛起等待。
  • pthread_mutex_unlock(pthread_mutex_t* mutex);釋放鎖
  • pthread_mutex_destroy(pthread_mutex_t* *mutex);使用完後釋放

經常使用:

pthread_mutex:互斥鎖

example: 

//建立鎖
__block pthread_mutex_t theLock; pthread_mutex_init(
&theLock, NULL);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ pthread_mutex_lock(&theLock); NSLog(@"須要線程同步的操做1 開始"); sleep(3); NSLog(@"須要線程同步的操做1 結束"); pthread_mutex_unlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&theLock); NSLog(@"須要線程同步的操做2"); pthread_mutex_unlock(&theLock); });

pthread_mutex(recursive):遞歸鎖

example: 

//注意:這是pthread_mutex爲了防止在遞歸的狀況下出現死鎖而出現的遞歸鎖。做用和NSRecursiveLock遞歸鎖相似。
//若是使用pthread_mutex_init(&theLock, NULL)初始化鎖的話,下面的代碼會出現死鎖現象,可是改爲使用遞歸鎖的形式,則沒有問題。

//建立鎖 
__block pthread_mutex_t theLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置成遞歸類型
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
    static void (^RecursiveMethod)(int);
    RecursiveMethod = ^(int value) {
        pthread_mutex_lock(&theLock);
        if (value > 0) {
            NSLog(@"value = %d", value);
            sleep(1);
            RecursiveMethod(value - 1);
        }
        pthread_mutex_unlock(&theLock);
   };
   RecursiveMethod(5);
});

 

3、性能

點擊這裏,參考網址

  • No1.自旋鎖OSSpinLock耗時最少
  • No2.pthread_mutex
  • No3.NSLock、NSCondition、NSRecursiveLock耗時接近
  • No4. @synchronized
  • No5. NSConditionLock
  • 柵欄的性能並無很好,在實際開發中也不多用到。

自旋鎖是線程不安全的在 ibireme 的 再也不安全的 OSSpinLock有解釋,進一步的ibireme在文中也有提到蘋果在新系統中已經優化了 pthread_mutex 的性能,因此它看上去和 OSSpinLock 差距並無那麼大。
相關文章
相關標籤/搜索