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