iOS_鎖:基礎的9種鎖,擴展12種使用

首先理解幾個鎖的概念:

互斥鎖(mutexlock)sleep-waiting:

保證共享數據操作的完整性, 鎖被佔用的時候會休眠, 等待鎖釋放的時候會喚醒。

在訪問共享資源之前進行加鎖,訪問完成後解鎖。

加鎖後,任何其他試圖加鎖的線程會被阻塞,直到當前線程解鎖。

解鎖時,如果有1個以上的線程阻塞,那麼所有該鎖上的線程變爲就緒狀態,第一個就緒的加鎖,其他的又進入休眠。

從而實現在任意時刻,最多隻有1個線程能夠訪問被互斥鎖保護的資源。

自旋鎖(spinlock)busy-waiting:

跟互斥類似, 只是資源被佔用的時候, 會一直循環檢測鎖是否被釋放(CPU不能做其他的事情)

節省了喚醒睡眠線程的內核消耗(在加鎖時間短暫的情況下會大大提高效率)

讀寫鎖(rwlock):

顧名思義 - 寫的時候:讀寫都等待;讀的時候:寫等待,讀無需等待

適合讀次數遠遠大於寫的情況 (爲了公平,應該設計爲:寫操作到來時,後面的讀阻塞)

條件鎖(condlock):

線程會因爲條件變量不滿足而阻塞,線程也可以在釋放鎖時將條件變量改成某個值,從而喚醒滿足條件變量的線程

遞歸鎖(recursivelock):

跟互斥類似, 但是允許同一個線程在未釋放鎖前,加鎖N次鎖, 不會引發死鎖

死鎖:

這裏給出的是同一個線程,在解鎖之前,多次上鎖的例子:會導致死鎖。下面任意一個遞歸鎖都可以解決這個導致死鎖的問題。

NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    [lock lock];
    if (value > 0) {
      NSLog(@"加鎖 %d", value);
      sleep(2);
      RecursiveMethod(value - 1); // 解鎖之前又加想鎖, 需要等待鎖的解除,
    }
    NSLog(@"解鎖 %d", value);
    [lock unlock];
  };
  RecursiveMethod(5);
});

 

接下來按目前的耗時排序介紹,先耗時短的:

1、OSSpinLock 自旋鎖

會造成優先級反轉的問題,iOS10+已廢棄,有興趣可以看看:不再去安全的OSSpinLock。這裏也給出一下使用例子:

NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
__block OSSpinLock osslock = OS_SPINLOCK_INIT; // 初始化
for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    OSSpinLockLock(&osslock); // 加鎖
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    OSSpinLockUnlock(&osslock); // 解鎖
  });
}

2、os_unfair_lock 互斥鎖 iOS10+

iOS10+才支持,爲了代替OSSpinLock,需要: #import <os/lock.h>。使用例子:

for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); // 必須在線程裏初始化
    os_unfair_lock_lock(unfairLock); // 加鎖
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"線程1 資源1: %@", self.items);
    os_unfair_lock_unlock(unfairLock); // 解鎖
  });
}

3、dispatch_semaphore 信號量

保證關鍵代碼不併發執行。使用例子:

NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 加鎖
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    dispatch_semaphore_signal(semaphore); // 解鎖
  });
}

4、pthread_mutex 互斥鎖

蘋果做出了優化, 性能不比semaphore差, 而且肯定安全(它有兩種初始化方法,第二種帶參數的可以設置type,下面會介紹)。需要:#import <pthread.h>。使用例子:

pthread_mutex_t _pthread;
pthread_mutex_init(&_pthread, NULL); // _pthread 不能是局部變量!!
NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&_pthread); // 加鎖
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    pthread_mutex_unlock(&_pthread); // 解鎖
  });
}

5、NSLock 互斥鎖

很方便,競爭的是這個鎖對象。使用例子:

self.lock = [[NSLock alloc] init];
self.lock.name = @"itemsLock";

NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self.lock lock]; // 加鎖
    sleep(items.count - i);
    [self.items addObject:items[i]];
    NSLog(@"%@", self.items);
    [self.lock unlock]; // 解鎖
  });
}

6、NSCondition 條件鎖

內部有實現NSLocking協議, 就能實現NSLock所有功能。使用例子:

NSCondition *lock = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 1");
  [lock lock];
//    [lock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; // 讓當前線程等待一段時間
  [lock wait];
  NSLog(@"end 1");
  [lock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 2");
  [lock lock];
  [lock wait];
  NSLog(@"end 2");
  [lock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  NSLog(@"start 3");
  sleep(2);
//    [lock signal]; // 喚醒一個等待的線程
  [lock broadcast]; // 喚醒所有等待的線程
  NSLog(@"end 3");
});

7、pthread_mutex(recursive) 遞歸鎖

需要設置 type = PTHREAD_MUTEX_RECURSIVE。

pthread_mutex_t plock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); // 初始化attr, 並賦予默認
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 設置type爲遞歸鎖
pthread_mutex_init(&plock, &attr); // 若爲 pthread_mutex_init(&plock, NULL) 則會死鎖
pthread_mutexattr_destroy(&attr); // 銷燬一個屬性對象,在重新初始化之前該結構不能重複使用

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    NSLog(@"加鎖: %d", value);
    pthread_mutex_lock(&plock); // 加鎖
    if (value < 5) {
      sleep(2);
      RecursiveMethod(++value); // 解鎖之前又加想鎖, 需要等待鎖的解除,
    }
    NSLog(@"解鎖: %d", value);
    pthread_mutex_unlock(&plock); // 解鎖
  };
  RecursiveMethod(1);
  NSLog(@"finish");
});

atrr的始終類型:

  •   PTHREAD_MUTEX_NORMAL: 互斥鎖不會檢測死鎖。

   當一個線程解鎖之前又鎖上,將導致死鎖。

   嘗試解除其他線程上的鎖,結果不可預測。

   嘗試解除一個未鎖定的鎖,結果不可預測。

  

  •   PTHREAD_MUTEX_ERRORCHECK: 互斥鎖提供錯誤檢查。

   當一個線程嘗試重新鎖定一個還未解開的鎖時,將會返回一個錯誤。

   嘗試解除其他線程上的鎖,將會返回一個錯誤。

   嘗試解除一個未鎖定的鎖,將會返回一個錯誤。

  

  •   PTHREAD_MUTEX_RECURSIVE: 遞歸鎖

   一個線程可以多次鎖定一個還未解開的鎖,需要相同數量的解鎖來釋放鎖,然後另一個線程才能獲的互斥鎖

   嘗試解除其他線程上的鎖,將會返回一個錯誤。

   嘗試解除一個未鎖定的鎖,將會返回一個錯誤。

  

  •   PTHREAD_MUTEX_DEFAULT:

   嘗試遞歸鎖定此類型的鎖,結果不可預測。

   嘗試解除其他線程上的鎖,結果不可預測。

   嘗試解除一個未鎖定的鎖,結果不可預測。

 

  8、NSRecursiveLock 遞歸鎖

直接操作lock對象很方便,競爭的是這個鎖對象。使用例子:

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
//  [lock tryLock];
//  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:2]];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursiveMethod)(int);
  RecursiveMethod = ^(int value) {
    NSLog(@"加鎖: %d", value);
    [lock lock]; // 加鎖
    if (value < 5) {
      sleep(2);
      RecursiveMethod(++value); // 解鎖之前又鎖上
    }
    NSLog(@"解鎖: %d", value);
    [lock unlock]; // 解鎖
  };
  RecursiveMethod(1);
  NSLog(@"finish");
});

9、NSConditionLock 條件鎖

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; // 設置初始標誌位
//  [cLock lock]; // 不區分條件, 可加鎖就執行
//  [cLock unlock]; // 不會清空條件, 之後滿足條件的鎖還會執行
//  [cLock tryLockWhenCondition:n]; // 標誌位爲n時, 加鎖成功
//  [cLock unlockWithCondition:n]; // 解鎖, 並設置標誌位
//  [cLock lockWhenCondition:n]; // 標誌位爲n時加鎖, 執行之後代碼

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  if ([cLock tryLockWhenCondition:0]) {
    NSLog(@"任務1");
    [cLock unlockWithCondition:1];
  } else {
    NSLog(@"任務1 加鎖失敗");
  }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  [cLock lockWhenCondition:3];
  NSLog(@"任務2");
  [cLock unlockWithCondition:2];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  [cLock lockWhenCondition:1];
  NSLog(@"任務3");
  [cLock unlockWithCondition:3];
});

10、@synchronized 互斥鎖

用pthread_mutex_t實現的。

NSArray *items = @[@"1", @"2", @"3"];
self.items = [[NSMutableArray alloc] init];
for (int i = 0; i < items.count; i++) {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized (self.items) { // 加鎖
      sleep(items.count - i);
      [self.items addObject:items[i]];
      NSLog(@"%@", self.items);
    }
  });
}

11、POSIXConditions 條件鎖:互斥鎖 + 條件鎖

參考 線程被一個 互斥 和 條件 結合的信號來喚醒,當鎖和條件同時滿足時,才能執行:

- (void)POSIX_Codictions {
  ready_to_go = false;
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&condition, NULL);
  
  [self waitOnConditionFunction];
  [self signalThreadUsingCondition];
}
- (void)waitOnConditionFunction {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex); // Lock the mutex.
    while(ready_to_go == false) {
      NSLog(@"wait...");
      pthread_cond_wait(&condition, &mutex); // 休眠
    }
    NSLog(@"done");
    ready_to_go = false;
    pthread_mutex_unlock(&mutex);
  });
}
- (void)signalThreadUsingCondition {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex); // Lock the mutex.
    ready_to_go = true;
    NSLog(@"true");
    pthread_cond_signal(&condition); // Signal the other thread to begin work.
    pthread_mutex_unlock(&mutex);
  });
}

12、pthreadReadWrite 讀寫鎖

__block pthread_rwlock_t rwLock;
pthread_rwlock_init(&rwLock, NULL);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_wrlock(&rwLock);
  NSLog(@"3 寫 begin");
  sleep(3);
  NSLog(@"3 寫 end");
  pthread_rwlock_unlock(&rwLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_rdlock(&rwLock);
  NSLog(@"1 讀 begin");
  sleep(1);
  NSLog(@"1 讀 end");
  pthread_rwlock_unlock(&rwLock);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  pthread_rwlock_rdlock(&rwLock);
  NSLog(@"2 讀 begin");
  sleep(2);
  NSLog(@"2 讀 end");
  pthread_rwlock_unlock(&rwLock);
});

Demo github地址

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章