鎖的類別
- NSLocking
- NSLock
- NSConditionLock 條件鎖
- NSRecursiveLock 遞歸鎖
- NSCondition
- NSDistributedLock 分布鎖
- @sychronized
- dispatch_semaphore
- OSSpinLock 自旋鎖
- pthread_mutex
首先我們要實現一種簡單的線程訪問導致的數據混亂的情況
- (void)threadTestLearn
{
__block NSInteger flag = 0;
void (^positive)() = ^{
while (1) {
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
}
};
void (^negative)() = ^{
while (1) {
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
這個方法的打印結果,預期應該是
1 == 1
-1 == -1
1 == 1
而事實上,由于沒有互斥訪問機制,打印結果出現了
1 == -1
-1 == 1
NSLocking
諸如 NSLock、NSConditionLock 等鎖都繼承了 NSLocking 協議,NSLocking 協議簡單粗暴地定義了兩個方法
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
一個是加鎖,一個是解鎖。
NSLock
同一個 NSLock 對象顯式調用 lock 函數后,除非自己顯示調用 unlock 函數,否則其他線程均不能繼續對此 NSLock 對象加鎖,從而達到互斥訪問的目的。 所以使用也很簡單
- (void)threadTestLearn
{
__block NSInteger flag = 0;
NSLock *lock = [NSLock new];
void (^positive)() = ^{
while (1) {
[lock lock];
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
[lock unlock];
}
};
void (^negative)() = ^{
while (1) {
[lock lock];
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
[lock unlock];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
如果希望獲取鎖時不阻塞線程,則考慮使用 tryLock 方法,它會在獲取鎖失敗后返回 NO,可以繼續執行后面的代碼。
lockBeforeDate 方法在指定的時間以前得到鎖。YES: 在指定時間之前獲得了鎖;NO: 在指定時間之前沒有獲得鎖。該線程將被阻塞,直到獲得了鎖,或者指定時間過期。
NSLockConditionLock
NSConditionLock 是一種條件鎖,除了基本的lock與unlock函數,還提供了 lockWithCondition 以及 unlockWithCondition 方法,適合多個線程的工作需要按照順序執行的情景。其中 unLockWithCondition 會把 condition 變量修改為指定參數后解鎖。
- (void)threadTestLearn
{
__block NSInteger flag = 0;
__block NSInteger mutext = 1;
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:mutext];
void (^positive)() = ^{
while (1) {
[lock lockWhenCondition:1];
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
[lock unlockWithCondition:-1];
}
};
void (^negative)() = ^{
while (1) {
[lock lockWhenCondition:-1];
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
[lock unlockWithCondition:1];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}
這里通過 mutex 變量嚴格保證了 positive 和 negative 方法會依次調用,并且第一個調用的方法一定是 positive 方法,同時如果將 negative 中的語句改成
[lock unlockWithCondition:0];
則兩個方法都只會被調用一次就停止了,因為都獲得不了正確的 condition 所以無法獲得鎖。
NSRecursiveLock
NSRecursiveLock 是遞歸鎖,用于解決遞歸調用中的死鎖問題。當一個方法加鎖以后遞歸調用自己,會再次進行加鎖,由于鎖沒有被釋放所以線程會被阻塞掉,線程阻塞后將永遠不能解鎖,因此發生死鎖。
NSRecursiveLock 實際上定義的是一個遞歸鎖,主要是用在循環或遞歸操作中。它可以允許同一線程多次加鎖,而不會造成死鎖。遞歸鎖會跟蹤它被 lock 的次數。每次成功的 lock 都必須平衡調用 unlock 操作。只有所有達到這種平衡,鎖最后才能被釋放,以供其它線程使用。
NSRecursiveLock *recursiveLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
static void (^test)(int);
test = ^(int value)
{
[recursiveLock lock];
if (value > 0)
{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%d", value);
test(--value);
}
else
{
[recursiveLock unlock];
return;
}
NSLog(@"%d", value);
[recursiveLock unlock];
};
__block int value = 5;
test(value);
});
NSCondition
NSCondition 與 NSConditionLock 是兩回事,它們之間最大的區別在于 NSCondition 支持在獲得鎖之后阻塞線程并放棄鎖,然后等待被喚醒后可以重新獲得鎖。
這里被喚醒有兩種方式,signal 和 broadcast,前者只會喚醒一個 wait 的線程,而后者會喚醒所有 wait 的線程。
但是有一個問題:在多個線程 wait 的場景下,一個線程調用了 broadcast 后如何喚醒多個 wait 的線程,能否保證線程安全,以及這個線程在未 unlock 前執行操作如何保證線程安全。
- (void)threadTestLearn
{
__block NSInteger flag = 0;
__block NSInteger mutex = 1;
NSCondition *condition = [NSCondition new];
void (^positive)() = ^{
while (1) {
[condition lock];
NSLog(@"thread1");
while (mutex == 0)
{
[condition wait];
}
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
mutex = 0;
[condition signal];
[condition unlock];
}
};
void (^negative)() = ^{
while (1) {
[condition lock];
NSLog(@"thread-1");
if (mutex == 0)
{
[condition wait];
}
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
mutex = 0;
[condition signal];
[condition unlock];
}
};
void (^zero)() = ^{
while (1) {
[condition lock];
if (mutex != 0)
{
[condition wait];
}
NSLog(@"mutex 0");
[NSThread sleepForTimeInterval:1];
mutex = 1;
[condition broadcast];
[condition unlock];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), zero);
}
@synchronized
@synchronized 可以保證唯一標識下的資源互斥訪問,不需要顯式創建鎖對象,使用方便,但是表現性能不佳。
__block NSInteger flag = 0;
void (^positive)() = ^{
while (1) {
@synchronized (@(flag)) {
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
}
}
};
void (^negative)() = ^{
while (1) {
@synchronized (@(flag)) {
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
}
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
dispatch_semaphore
dispatch_semaphore 是 GCD 中提供的信號量機制,通過信號量控制互斥訪問,提供了 wait 和 signal 方法,類似于信號量的 PV 操作。出于性能考慮建議開發中使用這個作為鎖。
- wait 對信號量減一,如小于 0 則等待
- signal 如果沒有等待接受信號的線程,則對信號量加一
__block NSInteger flag = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
void (^positive)() = ^{
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
dispatch_semaphore_signal(semaphore);
}
};
void (^negative)() = ^{
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
dispatch_semaphore_signal(semaphore);
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
OSSpinLock
自旋鎖,當無法獲得鎖時會一直空循環,占用 CPU,性能最高,適合執行輕量級工作,但是有隱藏的線程安全問題。
使用時需要引入包 #import <libkern/OSAtomic.h>。
__block NSInteger flag = 0;
__block OSSpinLock lock = OS_SPINLOCK_INIT;
void (^positive)() = ^{
while (1) {
OSSpinLockLock(&lock);
[NSThread sleepForTimeInterval:1];
flag = 1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"1 == %ld", flag);
OSSpinLockUnlock(&lock);
[NSThread sleepForTimeInterval:1];
}
};
void (^negative)() = ^{
while (1) {
OSSpinLockLock(&lock);
[NSThread sleepForTimeInterval:1];
flag = -1;
[NSThread sleepForTimeInterval:0.5];
NSLog(@"-1 == %ld", flag);
OSSpinLockUnlock(&lock);
[NSThread sleepForTimeInterval:1];
}
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
pthread_mutex
這是 C 語言中定義的互斥鎖,對于未獲得鎖的線程不使用忙等,而是直接睡眠。
首先引入頭文件 #import <pthread.h>
然后初始化 lock 變量
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
__block pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
這里 attr 有幾種 type
PTHREAD_MUTEX_NORMAL 缺省類型,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后先進先出原則獲得鎖。
PTHREAD_MUTEX_ERRORCHECK 檢錯鎖,如果同一個線程請求同一個鎖,則返回 EDEADLK,否則與普通鎖類型動作相同。這樣就保證當不允許多次加鎖時不會出現嵌套情況下的死鎖。
PTHREAD_MUTEX_RECURSIVE 遞歸鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次 unlock 解鎖。
PTHREAD_MUTEX_DEFAULT 適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭,沒有等待隊列。
加鎖解鎖過程類似其他鎖
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_mutex_destroy 為釋放鎖資源。