今天簡單寫一下iOS中相關鎖的內容作瞄,下圖來自不再安全的 OSSpinLock中幾種常見的鎖加解鎖的時間茶宵。
以下為我自己測試的結果,加上了iOS10以后的 os_unfair_lock_lock
OSSpinLock: 333.91 ms
os_unfair_lock_lock: 420.40 ms
dispatch_semaphore: 374.38 ms
pthread_mutex: 459.84 ms
NSCondition: 465.79 ms
NSLock: 470.10 ms
pthread_mutex(recursive): 800.02 ms
NSRecursiveLock: 712.30 ms
//多次測試NSRecursiveLock與pthread_mutex的遞歸鎖各有先后宗挥,水平有限不知道為啥
//還請有知道的大神不吝賜教乌庶。
NSConditionLock: 1581.54 ms
@synchronized: 1980.12 ms
---- 加解鎖 (10000000)次 測試設備 6s-iOS11.4 ----
廢棄的OSSpinLock
OSSpinLock(自旋鎖)是屬于busy-waiting類型的鎖种蝶,與互斥鎖不同,當SpinLock被其它線程持有瞒大,spinLock不會被阻塞螃征,而會一直的請求獲取lock,從而消耗大量cpu資源透敌。所以當臨界區(qū)任務時間較長時盯滚,并不適合用SpinLock。但當任務時間較短酗电,其效率賊高魄藕。
另外開頭已經提到 OSSpinLock之所以不再使用,是因為當?shù)蛢?yōu)先級的線程獲得了鎖顾瞻,這時一個高優(yōu)先級的線程也嘗試獲得這個鎖泼疑,它會處于 spin lock 的忙等狀態(tài)從而占用大量 CPU,此時低優(yōu)先級線程無法與高優(yōu)先級線程爭奪 CPU 時間荷荤,導致任務無法完成退渗。這就是優(yōu)先級反轉。
時間片輪轉算法蕴纳,每個線程會被分配一段時間片(quantum)会油,通常在 10-100 毫秒左右。當線程用完屬于自己的時間片以后,就會被操作系統(tǒng)掛起,放入等待隊列中梯投,直到下一次被分配時間片。
os_unfair_lock_t
os_unfair_lock_t是官方推薦的替代OSSpinLock的方案嫂冻,優(yōu)化了優(yōu)先級反轉問題。
os_unfair_lock_t lock = &(OS_UNFAIR_LOCK_INIT);;
os_unfair_lock_lock(lock);
os_unfair_lock_unlock(lock);
dispatch_semaphore
dispatch_semaphore 是信號量塞椎,但當信號總量設為 1 時也可以當作鎖來使用桨仿。在沒有等待情況出現(xiàn)時,它的性能比 pthread_mutex 還要高案狠。但一旦有等待情況出現(xiàn)時服傍,會線程進入睡眠狀態(tài),主動讓出時間片骂铁,讓出時間片會導致操作系統(tǒng)切換到另一個線程吹零,這就是所謂的上下文切換,通常需要 10 微秒左右拉庵,而且至少需要兩次切換灿椅。如果等待時間很短,比如只有幾個微秒,忙等就比線程睡眠更高效阱扬。
關于dispatch_semaphore在深入理解GCD中寫的也很詳細泣懊。
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(lock);
NSLock & pthread_mutex
POSIX線程(POSIX threads),簡稱Pthreads麻惶,是線程的POSIX標準。該標準定義了創(chuàng)建和操縱線程的一整套API信夫。
pthread_mutex 表示互斥鎖窃蹋。互斥鎖的實現(xiàn)原理與信號量非常相似静稻,不是使用忙等警没,而是阻塞線程并睡眠,需要進行上下文切換振湾。
NSLock底層也是使用pthread_mutex實現(xiàn)杀迹,屬性為PTHREAD_MUTEX_ERRORCHECK,因為多了方法發(fā)送等流程押搪,多次調用后因為方法緩存兩者的差距很小树酪。
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
NSLock *lock = [NSLock new];
[lock lock];
[lock unlock];
pthread_mutex(recursive)& NSRecursiveLock
NSRecursiveLock與NSLock類似,也是使用pthread_mutex實現(xiàn)大州,只是類型為 PTHREAD_MUTEX_RECURSIVE续语。
一般情況下,一個線程只能申請一次鎖厦画,也只能在獲得鎖的情況下才能釋放鎖疮茄,多次申請鎖或釋放未獲得的鎖都會導致崩潰。假設在已經獲得鎖的情況下再次申請鎖根暑,線程會因為等待鎖的釋放而進入睡眠狀態(tài)力试,因此就不可能再釋放鎖,從而導致死鎖排嫌。
然而這種情況經常會發(fā)生畸裳,比如某個函數(shù)申請了鎖,在臨界區(qū)內又遞歸調用了自己躏率,由此也就引出了遞歸鎖:允許同一個線程在未釋放其擁有的鎖時反復對該鎖進行加鎖操作躯畴。
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
//do work
pthread_mutex_unlock(&lock);
NSCondition
NSCondition 其實是封裝了一個互斥鎖和條件變量, 它把前者的 lock 方法和后者的 wait/signal 統(tǒng)一在 NSCondition 對象中薇芝,暴露給使用者蓬抄。所以與NSLock基本類似,性能也很接近夯到。
NSCondition *lock = [NSCondition new];
[lock lock];
//do work
[lock unlock];
NSConditionLock 借助 NSCondition 來實現(xiàn)嚷缭,它的本質就是一個生產者-消費者模型。“條件被滿足”可以理解為生產者提供了新的內容阅爽。NSConditionLock 的內部持有一個 NSCondition 對象路幸,以及 _condition_value 屬性,在初始化時就會對這個屬性進行賦值付翁。
它的 lockWhenCondition 方法其實就是消費者方法:
- (void) lockWhenCondition: (NSInteger)value {
[_condition lock];
while (value != _condition_value) {
[_condition wait];
}
}
對應的 unlockWhenCondition 方法則是生產者简肴,使用了 broadcast 方法通知了所有的消費者:
- (void) unlockWithCondition: (NSInteger)value {
_condition_value = value;
[_condition broadcast];
[_condition unlock];
}
@synchronized
從上圖可以看出@synchronized 性能是最差,語法最為簡單
每個調用 sychronized 的對象百侧,Objective-C runtime 都會為其分配一個遞歸鎖并存儲在哈希表中砰识。
具體實現(xiàn)可以看這關于 @synchronized,這兒比你想知道的還要多
@synchronized(object) {
//do work
}