本文不介紹各種鎖的高級用法,只是整理鎖相關的知識點,幫助理解。
鎖的作用
防止在多線程(多任務)的情況下對共享資源(臨界資源)的臟讀或者臟寫。
自旋鎖和互斥鎖
共同點:都能保證同一時刻只能有一個線程操作鎖住的代碼。都能保證線程安全。
不同點:
- 互斥鎖(mutex):當上一個線程的任務沒有執行完畢的時候(被鎖住),那么下一個線程會進入睡眠狀態等待任務執行完畢(sleep-waiting),當上一個線程的任務執行完畢,下一個線程會自動喚醒然后執行任務。
- 自旋鎖(Spin lock):當上一個線程的任務沒有執行完畢的時候(被鎖住),那么下一個線程會一直等待(busy-waiting),當上一個線程的任務執行完畢,下一個線程會立即執行。
- 由于自旋鎖不會引起調用者睡眠,所以自旋鎖的效率遠高于互斥鎖
- 自旋鎖會一直占用CPU,也可能會造成死鎖
自旋鎖有bug! ibireme大神的文章《不再安全的 OSSpinLock》
不同優先級線程調度算法會有優先級反轉問題,比如低優先級獲鎖訪問資源,高優先級嘗試訪問時會等待,這時低優先級又沒法爭過高優先級導致任務無法完成lock釋放不了。
1. 原子操作
-
nonatomic
:非原子屬性,非線程安全,適合小內存移動設備 -
atomic
:原子屬性,default,線程安全(內部使用自旋鎖),消耗大量資源單寫多讀,只為setter方法加鎖,不影響getter
-
相關代碼如下:
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); }
很容易理解的代碼,可變拷貝和不可變拷貝會開辟新的空間,兩者皆不是則持有(引用計數+1),相比nonatomic
只是多了一步鎖操作。
2. synchronized 條件鎖
使用最簡單,性能也最差。
@synchronized(obj) {
// 內部會添加異常處理,所以耗時
NSLog(@"自動加鎖,自動解鎖,自動銷毀");
}
obj為該鎖的唯一標識,只有當標識相同時,才為滿足互斥
3. dispatch_semaphore 信號量
用于線程同步,有序訪問。不支持遞歸。
- 創建信號:
dispatch_semaphore_create(long value)
傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout
,時間到后會執行其后的語句 - 等待信號到達:
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
使得signal
值-1
- 發送信號,解除等待狀態:
dispatch_semaphore_signal(dispatch_semaphore_t deem)
使得signal
值+1
這里順便寫一下
dispatch_barrier
,一個dispatch_barrier
允許在一個并發隊列(如果是串行隊列或全局隊列相當于dispatch_(a)sync
)中創建一個同步點。當在并發隊列中遇到一個barrier,他會延遲執行barrier的block,等待所有在barrier之前提交的blocks執行結束。 這時,barrier block自己開始執行。 之后, 隊列繼續正常的執行操作。
dispatch_barrier_async
和dispatch_barrier_sync
的區別在于是否會等待自己的block完成再執行后面的任務。
一個同步訪問網絡的例子
- (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
,由于自旋鎖的優先級反轉問題后改用pthread_mutex
)
使用需導入頭文件:
#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) 遞歸鎖
遞歸鎖允許同一個線程在未釋放其擁有的鎖時反復對該鎖進行加鎖操作。不會造成死鎖。
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型,這邊是設置為遞歸鎖
pthread_mutex_init(&pLock, &attr); // 初始化的時候帶入參數
pthread_mutexattr_destroy(&attr); //銷毀一個屬性對象,在重新進行初始化之前該結構不能重新使用
6. OSSpinLock 自旋鎖
是性能最佳的鎖,但由于線程調度算法(高優先級線程始終會在低優先級線程前執行,一個線程不會受到比它更低優先級線程的干擾)優先級反轉的原因逐漸被其他鎖取代。不支持遞歸。
使用需導入頭文件
#import <libkern/OSAtomic.h>
-
OSSpinLock oslock = OS_SPINLOCK_INIT;
:默認值為 0,在 locked 狀態時就會大于 0,unlocked狀態下為 0 -
OSSpinLockLock(&oslock);
:上鎖 -
OSSpinLockUnlock(&oslock);
:解鎖 -
OSSpinLockTry(&oslock)
:嘗試加鎖,可以加鎖則立即加鎖并返回YES
,反之返回NO
提一下
os_unfair_lock
,蘋果解決優先級反轉的問題整出的。。
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
7. NSLock
Cocoa提供的最基本的鎖對象,實際是在內部封裝了pthread_mutex
。對象鎖均實現了NSLocking
協議。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
-
trylock
:能加鎖返回YES
并執行加鎖操作,相當于lock
,反之返回NO
-
lockBeforeDate
:表示會在傳入的時間內嘗試加鎖,若能加鎖則執行加鎖操作并返回YES
,反之返回NO
8. NSRecursiveLock 遞歸鎖
NSLock
的遞歸版本,解決在循環或遞歸時造成的死鎖問題。使用同上。
9. NSCondition
最基本的條件鎖,底層是通過條件變量(condition variable) pthread_cond_t 來實現,實際上封裝了一個互斥鎖和條件變量。手動控制線程wait
和signal
-
wait
:進入等待狀態 -
waitUntilDate:
:讓一個線程等待一定的時間 -
signal
:喚醒一個等待的線程 -
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
性能相對較差,但使用NSConditionLock
可以處理任務間的依賴關系。
Additional
11. pthread_rwlock 讀寫鎖
讀寫鎖是用來解決文件讀寫問題的,讀操作可以共享,寫操作是排他的,讀可以有多個在讀,寫只有唯一個在寫,同時寫的時候不允許讀。
- 當讀寫鎖被一個線程以讀模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程還可以繼續進行
- 當讀寫鎖被一個線程以寫模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程也被阻塞
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 讀模式
pthread_rwlock_wrlock(&lock);
// 寫模式
pthread_rwlock_rdlock(&lock);
// 讀模式或者寫模式的解鎖
pthread_rwlock_unlock(&lock);
對于讀數據比修改數據頻繁的應用,用讀寫鎖代替互斥鎖可以提高效率。因為使用互斥鎖時,即使是讀出數據(相當于操作臨界區資源)都要上互斥鎖,而采用讀寫鎖,則可以在任一時刻允許多個讀出者存在,提高了更高的并發度,同時在某個寫入者修改數據期間保護該數據,以免任何其它讀出者或寫入者的干擾。
12. NSDistributedLock 分布式鎖
Mac開發使用,mark