在 iOS 中,鎖是多線程編程中的關鍵要素,也是保證線程安全的重要手段之一。本文將介紹常見的幾種鎖及其使用場景,并提供相關的代碼示例。
iOS中的鎖主要可以分為兩大類,互斥鎖和自旋鎖,其他鎖都是這兩種鎖的延伸和擴展。
一、自旋鎖
原理
自旋鎖是一種基于忙等待的鎖,當多個線程訪問共享資源時,自旋鎖會不停地進行循環檢查,直到獲取到鎖為止。自旋鎖的好處在于它避免了線程切換和上下文切換的開銷,在多核 CPU 上,自旋鎖可以充分利用 CPU 時間片,因此在鎖競爭不激烈的情況下,自旋鎖的性能比互斥鎖好。
使用場景
自旋鎖適用于以下場景:
- 多個線程訪問共享資源的競爭不激烈,即鎖競爭不激烈。
- 訪問共享資源的時間很短,即不會發生線程掛起。
iOS只有一種自旋鎖:OSSpinLock,其使用方法如下:
#import <libkern/OSAtomic.h>
OSSpinLock spinLock = OS_SPINLOCK_INIT;
// 獲取鎖
OSSpinLockLock(&spinLock);
// 臨界區代碼
// 釋放鎖
OSSpinLockUnlock(&spinLock);
需要注意的是,由于自旋鎖等待線程不會進入阻塞狀態,而是不斷嘗試獲取鎖,如果等待時間過長會導致CPU占用過高,從而影響應用程序的性能。此外,OSSpinLock還存在優先級反轉的問題。
優先級反轉指的是高優先級的線程因等待低優先級線程所持有的鎖而被阻塞的情況。在這種情況下,高優先級的線程可能一直等待,直到低優先級的線程釋放鎖。并且高優先級的線程會搶占CPU資源,低優先級的線程得不到CPU調度,進而延長了等待時間,導致高優先級的任務被延遲執行。
由于這些原因,OSSpinLock已被蘋果廢棄。
二、互斥鎖
互斥鎖是一種常見的鎖,用于保護臨界區代碼,防止多個線程同時訪問共享資源。
與自旋鎖不同的是,互斥鎖會將未獲得鎖的線程掛起,等待鎖的釋放。這種操作需要進行上下文切換,開銷較大,因此互斥鎖的性能不如自旋鎖。
iOS 中的常見互斥鎖包括 pthread_mutex_t、NSLock、NSCondition、NSConditionLock、NSRecursiveLock等。
1.pthread_mutex_t
pthread_mutex是一種互斥鎖,它可以保證同一時間只有一個線程可以訪問共享資源。pthread_mutex基于POSIX標準實現。
pthread_mutex與其他鎖機制相比,具有以下特點:
- 輕量級,適合高并發場景。
- 由于是C語言的庫,使用起來相對較復雜。
代碼示例
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 獲取鎖
pthread_mutex_lock(&mutex);
// 臨界區代碼
// 釋放鎖
pthread_mutex_unlock(&mutex);
2.NSLock
NSLock是一個簡單的互斥鎖,它基于POSIX線程互斥鎖實現。
代碼示例
NSLock *lock = [[NSLock alloc] init];
// 獲取鎖
[lock lock];
// 臨界區代碼
// 釋放鎖
[lock unlock];
3. NSCondition
NSCondition是一種條件鎖,它可以讓線程在某個條件滿足時等待,并在條件滿足時喚醒線程。NSCondition基于pthread_cond_t和pthread_mutex_t實現。
NSCondition適用于以下場景:
- 需要等待某個條件滿足后再進行訪問的場景。
- 支持多個條件等待和喚醒的場景。
代碼示例
// 初始化條件鎖和條件變量
NSCondition *condition = [[NSCondition alloc] init];
BOOL conditionMet = NO;
- (void)waitExample {
// 等待條件滿足
[condition lock];
while (!conditionMet) {
[condition wait];
}
// 訪問共享資源
// 釋放鎖
[condition unlock];
}
- (void)signalExample {
// 喚醒等待的線程
[condition lock];
conditionMet = YES;
[condition signal];
[condition unlock];
}
4. NSConditionLock
NSConditionLock是一種條件鎖,它可以讓線程在不同的條件下等待。NSConditionLock基于NSCondition和一個內部的狀態變量實現。
NSConditionLock與其他鎖機制相比,具有以下特點:
- 支持多個條件等待和喚醒。
- 支持在不同的條件下等待。
代碼示例
// 初始化條件鎖
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
- (void)conditionLockExample {
// 等待條件1
[conditionLock lockWhenCondition:0];
// 訪問共享資源
// 設置條件2
[conditionLock unlockWithCondition:1];
// 等待條件2
[conditionLock lockWhenCondition:1];
// 訪問共享資源
// 釋放鎖
[conditionLock unlock];
}
5. NSRecursiveLock
NSRecursiveLock是一種遞歸鎖,它允許同一個線程多次獲取鎖,避免死鎖。
NSRecursiveLock與其他鎖機制相比,具有以下特點:
- 支持遞歸鎖,同一線程可以多次獲取同一把鎖,避免死鎖。
- NSRecursiveLock的性能比NSLock略低,因為它需要維護額外的狀態以支持遞歸鎖。
代碼示例
// 初始化遞歸鎖
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
- (void)recursiveLockExample {
// 獲取遞歸鎖
[recursiveLock lock];
// 訪問共享資源
// 再次獲取遞歸鎖
[recursiveLock lock];
// 訪問共享資源
// 釋放遞歸鎖
[recursiveLock unlock];
// 釋放遞歸鎖
[recursiveLock unlock];
}
三. 信號量
原理
信號量是一種常見的并發控制機制,用于控制對共享資源的訪問。當多個線程訪問共享資源時,信號量可以用來限制并發訪問的數量。信號量有一個計數器,每個線程訪問共享資源前需要獲取信號量,如果計數器為0,則線程需要等待,直到其他線程釋放信號量為止。如果計數器不為0,則線程可以訪問共享資源,并將計數器減1。當線程訪問完共享資源后,需要釋放信號量,使計數器加1,以便其他線程可以訪問共享資源。
區別
與互斥鎖和自旋鎖不同的是,信號量可以控制對共享資源的并發訪問數量,因此它更適合用于限制并發度較高的情況。
使用場景
- 多個線程訪問共享資源的競爭激烈,即鎖競爭激烈。
- 需要控制并發訪問的數量。
代碼示例
// 初始化信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
- (void)lockExample {
// 等待信號量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 訪問共享資源
// 釋放信號量
dispatch_semaphore_signal(semaphore);
}
總結
本文介紹了iOS開發中常見的五種鎖機制:互斥鎖、自旋鎖、信號量、讀寫鎖和GCD同步鎖。對于每種鎖機制,我們講解了它的原理、區別、使用場景和代碼示例,以便開發者可以根據實際情況選擇合適的鎖機制來實現多線程訪問控制。在實際開發中,我們需要根據具體的場景選擇合適的鎖機制,以保證多線程程序的正確性和效率。