線程鎖使用場景:在多個線程下操作同一個數(shù)據(jù)耕蝉,數(shù)據(jù)將變得不安全韩肝。比方說:在多個線程中刪除一個數(shù)組的首個元素焊切,你不知道在多線程操作過程中砰碴,該元素還存不存在躏筏,如果不存在程序就會崩潰。
加了線程鎖以后呈枉,就能保證在A線程訪問數(shù)據(jù)的時候趁尼,B線程就沒有辦法訪問。只有在A線程執(zhí)行完解鎖操作以后猖辫,B線程才有資格去訪問酥泞。也就是說該數(shù)據(jù)只允許被一個線程訪問,這就是線程安全啃憎。
針對這個問題芝囤,我們一起來盤點下iOS開發(fā)中的線程鎖。
一辛萍、 原子鎖: atomic
atomic
是@property
創(chuàng)建屬性默認的關(guān)鍵字悯姊,使用atmoic
關(guān)鍵字會在屬性的setter方法里面加上了,如下面代碼贩毕。因為手機設(shè)備資源有限悯许,為了提高效率在iOS開發(fā)中我們一般上使用nonatomic
關(guān)鍵字。
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
Objective-C Property Attributes這篇文章中詳細解釋了atomic
和nonatomic
辉阶。劃重點:Atomic is really commonly confused with being thread-safe, and that is not correct. You need to guarantee your thread safety other ways.就是說用了atomic關(guān)鍵字并不能保證數(shù)據(jù)是線程安全的先壕,它只能保證你拿到的值是完好無損的。
- (void)multiOperation {
for (int i=0; i<10; i++) {
NSString *queue = [NSString stringWithFormat:@"queue-%d", i];
dispatch_queue_t q = dispatch_queue_create([queue UTF8String], NULL);
dispatch_async(q, ^{
self.string = queue;
});
}
}
調(diào)用多次的結(jié)果:
string === queue-1
string === queue-5
string === queue-4
string === (null)
string === (null)
string === queue-0
string === queue-8
string === queue-6
上面這個例子里面string屬性是nonatomic
修飾的谆甜,連續(xù)多次調(diào)用結(jié)果是隨機的垃僚,中間也會出現(xiàn)string === (null)
的情況。如果用atomic
來修飾結(jié)果也是隨機的但中間不會出現(xiàn)string === (null)
规辱,這也就解釋了上面的結(jié)論:atomic關(guān)鍵字并不能保證數(shù)據(jù)是線程安全的谆棺,它只能保證你拿到的值是完好無損的。
為什么會出現(xiàn)string === (null)
呢按摘?回到setter代碼塊包券,如果不加原子鎖該屬性在多線程賦值的過程中碰巧兩個線程接連執(zhí)行了release
操作纫谅,當該屬性的retainCount=0的時候也就釋放了,所以就出現(xiàn)了null值溅固。
二付秕、NSLock & NSCondition & NSConditionLock & NSRecursiveLock
這四個是蘋果封裝好的線程鎖對象,統(tǒng)一定義在NSFoundation -> NSLock.h
文件里面侍郭,都遵守了NSLocking
協(xié)議询吴。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
所以,基本使用也很類似:
- 初始化一個鎖對象
- 執(zhí)行上鎖操作
[xxx lock]
- 執(zhí)行解鎖操作
[xxx unlock]
不同的是使用場景:
NSLock:最簡單的線程鎖亮元,沒有復(fù)雜的需求使用它就好了猛计。
NSCondition & NSConditionLock:條件鎖,滿足一定條件觸發(fā)的線程鎖爆捞。
NSRecursiveLock:遞歸鎖奉瘤,在遞歸調(diào)用使用線程鎖很容易造成死鎖,遞歸鎖就是為了解決這些問題設(shè)計的煮甥。具體使用參照:NSRecursiveLock遞歸鎖的使用
三盗温、synchronized關(guān)鍵字
為了避免多個線程同時執(zhí)行同一段代碼,Objective-C提供了@synchronized()
成肘。它可以對一段代碼進行加鎖卖局,同一時間只允許一個線程執(zhí)行該代碼,其他試圖執(zhí)行該代碼的線程都會被阻塞双霍。和NSLock等線程鎖對比砚偶,@synchronized()
使用起來更加方便,可讀性更高洒闸。
至于@synchronized()
的底層實現(xiàn)染坯,可以看這篇文章關(guān)于 @synchronized,這兒比你想知道的還要多顷蟀。
四酒请、信號量 dispatch_semaphore_t
信號量(dispatch_semaphore):信號量是一個整形值并且具有一個初始計數(shù)值骡技,并且支持兩個操作:信號通知和等待鸣个。當一個信號量被信號通知,其計數(shù)會被增加布朦。當一個線程在一個信號量上等待時囤萤,線程會被阻塞(如果有必要的話),直至計數(shù)器大于零是趴,然后線程會減少這個計數(shù)涛舍。
在GCD中有三個函數(shù)是semaphore的操作,分別是:dispatch_semaphore_create:創(chuàng)建一個semaphore唆途,需要傳入一個long類型的數(shù)富雅,作為信號總量掸驱。
dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加1没佑。
dispatch_semaphore_wait:發(fā)送一個等待信號毕贼,讓信號總量減1
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(semaphore);
這段代碼創(chuàng)建了一個初始值為10的信號量,每一次循環(huán)都會發(fā)送一個等待信號蛤奢,并創(chuàng)建一個線程鬼癣,該線程執(zhí)行完以后發(fā)送一個信號。當創(chuàng)建了10個線程以后啤贩,for循環(huán)就會阻塞待秃,等待有線程執(zhí)行完以后才會繼續(xù)執(zhí)行。這就形成了對并發(fā)的控制痹屹,上面是創(chuàng)建了一個并發(fā)數(shù)為10的線程隊列章郁。如果要做一個并發(fā)數(shù)為1的線程鎖,只需要創(chuàng)建一個初始值為1的信號量就可以了志衍。
五驱犹、補充 POSIX(pthread_mutex) & OSSpinLock
POSIX(pthread_mutex):Linux 線程鎖詳解
OSSpinLock:不再安全的 OSSpinLock
PS:POSIX(pthread_mutex)Linux底層的API,復(fù)雜的多線程處理建議使用足画,并且可以封裝自己的多線程雄驹;OSSpinLock已經(jīng)出現(xiàn)了BUG,導(dǎo)致并不能完全保證是線程安全的淹辞。
DEMO:
Objective-C-ThreadLock
參考文章:
iOS多線程 -各種線程鎖的簡單介紹
深入理解 iOS 開發(fā)中的鎖