在日常開(kāi)發(fā)過(guò)程中匿刮,為了提升程序運(yùn)行效率,以及用戶體驗(yàn)探颈,我們經(jīng)常使用多線程熟丸。在使用多線程的過(guò)程中,難免會(huì)遇到資源競(jìng)爭(zhēng)問(wèn)題伪节。我們采用鎖的機(jī)制來(lái)確保線程安全光羞。
線程安全
當(dāng)一個(gè)線程訪問(wèn)數(shù)據(jù)的時(shí)候,其他的線程不能對(duì)其進(jìn)行訪問(wèn)怀大,直到該線程訪問(wèn)完畢纱兑。即,同一時(shí)刻化借,對(duì)同一個(gè)數(shù)據(jù)操作的線程只有一個(gè)潜慎。只有確保了這樣,才能使數(shù)據(jù)不會(huì)被其他線程污染蓖康。而線程不安全铐炫,則是在同一時(shí)刻可以有多個(gè)線程對(duì)該數(shù)據(jù)進(jìn)行訪問(wèn),從而得不到預(yù)期的結(jié)果蒜焊。
比如寫(xiě)文件和讀文件倒信,當(dāng)一個(gè)線程在寫(xiě)文件的時(shí)候,理論上來(lái)說(shuō)泳梆,如果這個(gè)時(shí)候另一個(gè)線程來(lái)直接讀取的話鳖悠,那么得到將是不可預(yù)期的結(jié)果。
為了線程安全优妙,我們可以使用鎖的機(jī)制來(lái)確保乘综,同一時(shí)刻只有同一個(gè)線程來(lái)對(duì)同一個(gè)數(shù)據(jù)源進(jìn)行訪問(wèn)。在開(kāi)發(fā)過(guò)程中我們通常使用以下幾種鎖套硼。
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- pthread_mutex
- pthread_rwlock
- POSIX Conditions
- OSSpinLock
- os_unfair_lock
- dispatch_semaphore
- @synchronized
信號(hào)量
在多線程環(huán)境下用來(lái)確保代碼不會(huì)被并發(fā)調(diào)用卡辰。在進(jìn)入一段代碼前,必須獲得一個(gè)信號(hào)量熟菲,在結(jié)束代碼前看政,必須釋放該信號(hào)量,其他想要想要執(zhí)行該代碼的線程必須等待直到前者釋放了該信號(hào)量抄罕。
以一個(gè)停車(chē)場(chǎng)的運(yùn)作為例。簡(jiǎn)單起見(jiàn)于颖,假設(shè)停車(chē)場(chǎng)只有三個(gè)車(chē)位呆贿,一開(kāi)始三個(gè)車(chē)位都是空的。這時(shí)如果同時(shí)來(lái)了五輛車(chē),看門(mén)人允許其中三輛直接進(jìn)入做入,然后放下車(chē)攔冒晰,剩下的車(chē)則必須在入口等待,此后來(lái)的車(chē)也都不得不在入口處等待竟块。這時(shí)壶运,有一輛車(chē)離開(kāi)停車(chē)場(chǎng),看門(mén)人得知后浪秘,打開(kāi)車(chē)攔蒋情,放入外面的一輛進(jìn)去,如果又離開(kāi)兩輛耸携,則又可以放入兩輛棵癣,如此往復(fù)。
在這個(gè)停車(chē)場(chǎng)系統(tǒng)中夺衍,車(chē)位是公共資源狈谊,每輛車(chē)好比一個(gè)線程,看門(mén)人起的就是信號(hào)量的作用沟沙。
互斥鎖
一種用來(lái)防止多個(gè)線程同一時(shí)刻對(duì)共享資源進(jìn)行訪問(wèn)的信號(hào)量河劝,它的原子性確保了如果一個(gè)線程鎖定了一個(gè)互斥量,將沒(méi)有其他線程在同一時(shí)間可以鎖定這個(gè)互斥量矛紫。它的唯一性確保了只有它解鎖了這個(gè)互斥量丧裁,其他線程才可以對(duì)其進(jìn)行鎖定。當(dāng)一個(gè)線程鎖定一個(gè)資源的時(shí)候含衔,其他對(duì)該資源進(jìn)行訪問(wèn)的線程將會(huì)被掛起煎娇,直到該線程解鎖了互斥量,其他線程才會(huì)被喚醒贪染,進(jìn)一步才能鎖定該資源進(jìn)行操作缓呛。
NSLock
NSLock實(shí)現(xiàn)了最基本的互斥鎖,遵循了 NSLocking
協(xié)議杭隙,通過(guò) lock
和 unlock
來(lái)進(jìn)行鎖定和解鎖哟绊。其使用也非常簡(jiǎn)單
- (void)doSomething {
[self.lock lock];
//TODO: do your stuff
[self.lock unlock];
}
由于是互斥鎖,當(dāng)一個(gè)線程進(jìn)行訪問(wèn)的時(shí)候痰憎,該線程獲得鎖票髓,其他線程進(jìn)行訪問(wèn)的時(shí)候,將被操作系統(tǒng)掛起铣耘,直到該線程釋放鎖洽沟,其他線程才能對(duì)其進(jìn)行訪問(wèn),從而卻確保了線程安全蜗细。但是如果連續(xù)鎖定兩次裆操,則會(huì)造成死鎖問(wèn)題怒详。那如果想在遞歸中使用鎖,那要怎么辦呢踪区,這就用到了 NSRecursiveLock
遞歸鎖昆烁。
NSRecursiveLock
遞歸鎖,顧名思義缎岗,可以被一個(gè)線程多次獲得静尼,而不會(huì)引起死鎖。它記錄了成功獲得鎖的次數(shù)传泊,每一次成功的獲得鎖鼠渺,必須有一個(gè)配套的釋放鎖和其對(duì)應(yīng),這樣才不會(huì)引起死鎖或渤。只有當(dāng)所有的鎖被釋放之后系冗,其他線程才可以獲得鎖
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
[theLock lock];
if (value != 0)
{
--value;
MyRecursiveFunction(value);
}
[theLock unlock];
}
MyRecursiveFunction(5);
NSCondition
NSCondition 是一種特殊類(lèi)型的鎖,通過(guò)它可以實(shí)現(xiàn)不同線程的調(diào)度薪鹦。一個(gè)線程被某一個(gè)條件所阻塞掌敬,直到另一個(gè)線程滿足該條件從而發(fā)送信號(hào)給該線程使得該線程可以正確的執(zhí)行。比如說(shuō)池磁,你可以開(kāi)啟一個(gè)線程下載圖片奔害,一個(gè)線程處理圖片。這樣的話地熄,需要處理圖片的線程由于沒(méi)有圖片會(huì)阻塞华临,當(dāng)下載線程下載完成之后,則滿足了需要處理圖片的線程的需求端考,這樣可以給定一個(gè)信號(hào)雅潭,讓處理圖片的線程恢復(fù)運(yùn)行。
- (void)download {
[self.condition lock];
//TODO: 下載文件代碼
if (donloadFinish) { // 下載結(jié)束后却特,給另一個(gè)線程發(fā)送信號(hào)扶供,喚起另一個(gè)處理程序
[self.condition signal];
[self.condition unlock];
}
}
- (void)doStuffWithDownloadPicture {
[self.condition lock];
while (!donloadFinish) {
[self.condition wait];
}
//TODO: 處理圖片代碼
[self.condition unlock];
}
NSConditionLock
NSConditionLock 對(duì)象所定義的互斥鎖可以在使得在某個(gè)條件下進(jìn)行鎖定和解鎖。它和 NSCondition
很像裂明,但實(shí)現(xiàn)方式是不同的椿浓。
當(dāng)兩個(gè)線程需要特定順序執(zhí)行的時(shí)候,例如生產(chǎn)者消費(fèi)者模型闽晦,則可以使用 NSConditionLock
扳碍。當(dāng)生產(chǎn)者執(zhí)行執(zhí)行的時(shí)候,消費(fèi)者可以通過(guò)特定的條件獲得鎖仙蛉,當(dāng)生產(chǎn)者完成執(zhí)行的時(shí)候笋敞,它將解鎖該鎖,然后把鎖的條件設(shè)置成喚醒消費(fèi)者線程的條件捅儒。鎖定和解鎖的調(diào)用可以隨意組合液样,lock
和 unlockWithCondition:
配合使用 lockWhenCondition:
和 unlock
配合使用振亮。
- (void)producer {
while (YES) {
[self.conditionLock lock];
NSLog(@"have something");
self.count++;
[self.conditionLock unlockWithCondition:1];
}
}
- (void)consumer {
while (YES) {
[self.conditionLock lockWhenCondition:1];
NSLog(@"use something");
self.count--;
[self.conditionLock unlockWithCondition:0];
}
}
當(dāng)生產(chǎn)者釋放鎖的時(shí)候巧还,把條件設(shè)置成了1鞭莽。這樣消費(fèi)者可以獲得該鎖,進(jìn)而執(zhí)行程序麸祷,如果消費(fèi)者獲得鎖的條件和生產(chǎn)者釋放鎖時(shí)給定的條件不一致澎怒,則消費(fèi)者永遠(yuǎn)無(wú)法獲得鎖,也不能執(zhí)行程序阶牍。同樣喷面,如果消費(fèi)者釋放鎖給定的條件和生產(chǎn)者獲得鎖給定的條件不一致的話,則生產(chǎn)者也無(wú)法獲得鎖走孽,程序也不能執(zhí)行惧辈。
pthread_mutex
POSIX 互斥鎖是一種超級(jí)易用的互斥鎖,使用的時(shí)候磕瓷,只需要初始化一個(gè) pthread_mutex_t
用 pthread_mutex_lock
來(lái)鎖定 pthread_mutex_unlock
來(lái)解鎖盒齿,當(dāng)使用完成后,記得調(diào)用 pthread_mutex_destroy
來(lái)銷(xiāo)毀鎖困食。
pthread_mutex_init(&lock,NULL);
pthread_mutex_lock(&lock);
//do your stuff
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
pthread_rwlock
讀寫(xiě)鎖边翁,在對(duì)文件進(jìn)行操作的時(shí)候,寫(xiě)操作是排他的硕盹,一旦有多個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫(xiě)操作符匾,后果不可估量,但讀是可以的瘩例,多個(gè)線程讀取時(shí)沒(méi)有問(wèn)題的啊胶。
- 當(dāng)讀寫(xiě)鎖被一個(gè)線程以讀模式占用的時(shí)候,寫(xiě)操作的其他線程會(huì)被阻塞垛贤,讀操作的其他線程還可以繼續(xù)進(jìn)行焰坪。
- 當(dāng)讀寫(xiě)鎖被一個(gè)線程以寫(xiě)模式占用的時(shí)候,寫(xiě)操作的其他線程會(huì)被阻塞南吮,讀操作的其他線程也被阻塞琳彩。
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER
// 讀模式
pthread_rwlock_wrlock(&lock);
// 寫(xiě)模式
pthread_rwlock_rdlock(&lock);
// 讀模式或者寫(xiě)模式的解鎖
pthread_rwlock_unlock(&lock);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:3];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:4];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:5];
});
- (void)readBookWithTag:(NSInteger )tag {
pthread_rwlock_rdlock(&rwLock);
NSLog(@"start read ---- %ld",tag);
self.path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@".doc"];
self.contentString = [NSString stringWithContentsOfFile:self.path encoding:NSUTF8StringEncoding error:nil];
NSLog(@"end read ---- %ld",tag);
pthread_rwlock_unlock(&rwLock);
}
- (void)writeBook:(NSInteger)tag {
pthread_rwlock_wrlock(&rwLock);
NSLog(@"start wirte ---- %ld",tag);
[self.contentString writeToFile:self.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"end wirte ---- %ld",tag);
pthread_rwlock_unlock(&rwLock);
}
start read ---- 1
start read ---- 2
end read ---- 1
end read ---- 2
start wirte ---- 3
end wirte ---- 3
start wirte ---- 4
end wirte ---- 4
start read ---- 5
end read ---- 5
POSIX Conditions
POSIX 條件鎖需要互斥鎖和條件兩項(xiàng)來(lái)實(shí)現(xiàn),雖然看起來(lái)沒(méi)什么關(guān)系部凑,但在運(yùn)行時(shí)中露乏,互斥鎖將會(huì)與條件結(jié)合起來(lái)。線程將被一個(gè)互斥和條件結(jié)合的信號(hào)來(lái)喚醒涂邀。
首先初始化條件和互斥鎖瘟仿,當(dāng) ready_to_go
為 flase
的時(shí)候,進(jìn)入循環(huán)比勉,然后線程將會(huì)被掛起劳较,直到另一個(gè)線程將 ready_to_go
設(shè)置為 true
的時(shí)候驹止,并且發(fā)送信號(hào)的時(shí)候,該線程會(huì)才被喚醒观蜗。
pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean ready_to_go = true;
void MyCondInitFunction()
{
pthread_mutex_init(&mutex);
pthread_cond_init(&condition, NULL);
}
void MyWaitOnConditionFunction()
{
// Lock the mutex.
pthread_mutex_lock(&mutex);
// If the predicate is already set, then the while loop is bypassed;
// otherwise, the thread sleeps until the predicate is set.
while(ready_to_go == false)
{
pthread_cond_wait(&condition, &mutex);
}
// Do work. (The mutex should stay locked.)
// Reset the predicate and release the mutex.
ready_to_go = false;
pthread_mutex_unlock(&mutex);
}
void SignalThreadUsingCondition()
{
// At this point, there should be work for the other thread to do.
pthread_mutex_lock(&mutex);
ready_to_go = true;
// Signal the other thread to begin work.
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
}
OSSpinLock
自旋鎖臊恋,和互斥鎖類(lèi)似,都是為了保證線程安全的鎖墓捻。但二者的區(qū)別是不一樣的抖仅,對(duì)于互斥鎖,當(dāng)一個(gè)線程獲得這個(gè)鎖之后砖第,其他想要獲得此鎖的線程將會(huì)被阻塞撤卢,直到該鎖被釋放。但自選鎖不一樣梧兼,當(dāng)一個(gè)線程獲得鎖之后放吩,其他線程將會(huì)一直循環(huán)在哪里查看是否該鎖被釋放。所以羽杰,此鎖比較適用于鎖的持有者保存時(shí)間較短的情況下渡紫。
// 初始化
spinLock = OS_SPINLOCK_INIT;
// 加鎖
OSSpinLockLock(&spinLock);
// 解鎖
OSSpinLockUnlock(&spinLock);
然而,YYKit 作者 @ibireme 的文章也有說(shuō)這個(gè)自旋鎖存在優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題忽洛,具體文章可以戳 不再安全的 OSSpinLock腻惠。
os_unfair_lock
自旋鎖已經(jīng)不在安全,然后蘋(píng)果又整出來(lái)個(gè) os_unfair_lock_t
(╯‵□′)╯︵┻━┻
這個(gè)鎖解決了優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題欲虚。
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
dispatch_semaphore
信號(hào)量機(jī)制實(shí)現(xiàn)鎖集灌,等待信號(hào),和發(fā)送信號(hào)复哆,正如前邊所說(shuō)的看門(mén)人一樣欣喧,當(dāng)有多個(gè)線程進(jìn)行訪問(wèn)的時(shí)候,只要有一個(gè)獲得了信號(hào)梯找,其他線程的就必須等待該信號(hào)釋放唆阿。
- (void)semphone:(NSInteger)tag {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
// do your stuff
dispatch_semaphore_signal(semaphore);
}
@synchronized
一個(gè)便捷的創(chuàng)建互斥鎖的方式,它做了其他互斥鎖所做的所有的事情锈锤。
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
如果你在不同的線程中傳過(guò)去的是一樣的標(biāo)識(shí)符驯鳖,先獲得鎖的會(huì)鎖定代碼塊,另一個(gè)線程將被阻塞久免,如果傳遞的是不同的標(biāo)識(shí)符浅辙,則不會(huì)造成線程阻塞。
總結(jié)
應(yīng)當(dāng)針對(duì)不同的操作使用不同的鎖阎姥,而不能一概而論那種鎖的加鎖解鎖速度快记舆。
- 當(dāng)進(jìn)行文件讀寫(xiě)的時(shí)候,使用
pthread_rwlock
較好呼巴,文件讀寫(xiě)通常會(huì)消耗大量資源泽腮,而使用互斥鎖同時(shí)讀文件的時(shí)候會(huì)阻塞其他讀文件線程御蒲,而pthread_rwlock
不會(huì)。 - 當(dāng)性能要求較高時(shí)候诊赊,可以使用
pthread_mutex
或者dispath_semaphore
厚满,由于OSSpinLock
不能很好的保證線程安全,而在只有在iOS10
中才有os_unfair_lock
豪筝,所以痰滋,前兩個(gè)是比較好的選擇摘能。既可以保證速度续崖,又可以保證線程安全。 - 對(duì)于 NSLock 及其子類(lèi)团搞,速度來(lái)說(shuō)
NSLock
<NSCondition
<NSRecursiveLock
<NSConditionLock
严望。
引用
1.Threading Programming Guide
2.百度百科-線程安全
3.百度百科-信號(hào)量
4.百度百科-互斥鎖
5.不再安全的 OSSpinLock