背景:在多線程中,如果同時(shí)搶一塊資源,比如給數(shù)組追加數(shù)據(jù),可變數(shù)組(字典)不是線程安全的,就會(huì)導(dǎo)致crash,為了避免這種問題,就衍生出線程同步,來(lái)保證線程安全。
先介紹幾種鎖,
第一種鎖
@synchronized(互斥鎖)
NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
? ? ? @synchronized(obj)?{
? ? ? ? ? ? ?NSLog(@"需要線程同步的操作1?開始");
? ? ? ? ? ? ?sleep(3);
? ? ? ? ? ? ?NSLog(@"需要線程同步的操作1?結(jié)束");
? ? ? }
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? ? ?sleep(1);
? ? ? ? ? ?@synchronized(obj) {
? ? ? ? ? ? ? ? ? NSLog(@"需要線程同步的操作2");
? ? ? ? ?}
});
1.@synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識(shí),@synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥,如果線程2中的@synchronized(obj)改為@synchronized(self),剛線程2就不會(huì)被阻塞,@synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。
2.synchronized 優(yōu)劣分析:
劣勢(shì):@synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。開銷大,性能差,不適合在生成環(huán)境使用。
優(yōu)點(diǎn):使用@synchronized關(guān)鍵字可以很方便地創(chuàng)建鎖對(duì)象,而且不用顯式的創(chuàng)建鎖對(duì)象。
3.synchronized 內(nèi)部的鎖是一個(gè)遞歸鎖。
4.atomic:原子屬性,為setter方法加鎖,的內(nèi)部實(shí)現(xiàn)就是synchronized。
第二種鎖
NSLock(性質(zhì):互斥鎖)
容易造成死鎖,比如:
//主線程中
NSLock *theLock = [[NSLock alloc] init];
TestObj *obj = [[TestObj alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? static void(^TestMethod)(int);
? ? ? ? TestMethod = ^(int value){
? ? ? ? ? ? ? ?[theLock lock];
? ? ? ? ? ? ? ?if (value > 0){
? ? ? ? ? ? ? ? ? ? ? [obj method1];
? ? ? ? ? ? ? ? ? ? ?sleep(5); //后面寫上[theLock unlock];而不是放在最后,就加鎖,解鎖就一一對(duì)應(yīng)了,
? ? ? ? ? ? ? ? ? ? TestMethod(value-1);
? ? ? ? ? ? ?}
? ? ? ? [theLock unlock];
? ? ? };
? ? ? ? TestMethod(5);
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? ?sleep(1);
? ? ? ? [theLock lock];
? ? ? ? ?[obj method2];
? ? ? ? [theLock unlock];
});
//簡(jiǎn)單分析:這段代碼是一個(gè)典型的死鎖情況。在我們的線程中1是遞歸調(diào)用的。所以每次進(jìn)入這個(gè)block時(shí),都會(huì)去加一次鎖,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導(dǎo)致了死鎖,
//調(diào)試器有這些提示:[NSLock lock]: deadlock
//如果在在遞歸或循環(huán)中正確的使用鎖:
NSRecursiveLock 遞歸鎖,直接NSLock *theLock = [[NSLock alloc] init];
替換成NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];就ok了
第三種鎖
NSConditionLock條件鎖
特殊場(chǎng)景使用,也就是滿足一定的條件下,才解鎖,和創(chuàng)建鎖。
第四種方式,用信號(hào)量來(lái)實(shí)現(xiàn)鎖的功能
信號(hào)量 ==>當(dāng)信號(hào)個(gè)數(shù)為 0 時(shí),則線程阻塞,等待發(fā)送新信號(hào);一旦信號(hào)個(gè)數(shù)大于 0 時(shí),就開始處理任務(wù)。
dispatch_semaphore_create、
dispatch_semaphore_wait? //在執(zhí)行完這行代碼后,信號(hào)量就會(huì)減一,如果信號(hào)量變成了0,那么就在這里等著,后面一般就是一些排他操作(比如:數(shù)組的寫操作,保證同步)
dispatch_semaphore_signal、 //作用:在排他操作結(jié)束后,現(xiàn)在就可以讓其他線程來(lái)玩了,執(zhí)行完這行代碼,信號(hào)量加1,之前其他等待的線程就又開始工作了。
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? ?dispatch_semaphore_wait(signal, overTime);
? ? ? ? ?NSLog(@"需要線程同步的操作1 開始");
? ? ? ? ? sleep(2); //數(shù)組寫操作
? ? ? ? ? NSLog(@"需要線程同步的操作1 結(jié)束");
? ? ? ? ?dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? ? sleep(1);
? ? ? ? ?dispatch_semaphore_wait(signal, overTime);
? ? ? ? ? NSLog(@"需要線程同步的操作2");
? ? ? ? ? dispatch_semaphore_signal(signal);
});
//結(jié)果就是:
2016-06-29 20:47:52.324 SafeMultiThread[35945:579032] 需要線程同步的操作1 開始
2016-06-29 20:47:55.325 SafeMultiThread[35945:579032] 需要線程同步的操作1 結(jié)束
2016-06-29 20:47:55.326 SafeMultiThread[35945:579033] 需要線程同步的操作2
//這里設(shè)置的 dispatch_semaphore_wait(signal, overTime);有個(gè)超時(shí)時(shí)間,一般可以寫作DISPATCH_TIME_FOREVER,永不超時(shí)。
//這里overtime 如果改成1*NSEC_PER_SEC,那么wait就不等了,就向后走了,
2016-06-30 18:53:24.049 SafeMultiThread[30834:434334] 需要線程同步的操作1 開始
2016-06-30 18:53:25.554 SafeMultiThread[30834:434332] 需要線程同步的操作2
2016-06-30 18:53:26.054 SafeMultiThread[30834:434334] 需要線程同步的操作1 結(jié)束
簡(jiǎn)單總結(jié)一下:
互斥鎖特點(diǎn):如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖。一旦被訪問的資源被解鎖,則等待資源的線程會(huì)被喚醒。
自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)以死循環(huán)的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行。性能最高的鎖,OSSpinLock已經(jīng)不再安全,不再使用。
信號(hào)量:一個(gè)線程完成了操作完共享數(shù)據(jù)后,就通過發(fā)出信號(hào)量告訴別的線程,你們現(xiàn)在可以用這塊共享資源了。
性能比較:OSSpinLock > dispatch_semaphore > NSLock > NSRecursiveLock > NSConditionLock > @synchronized.
生產(chǎn)環(huán)境一般推薦使用dispatch_semaphore。
上述都是解決同一時(shí)刻,只允許一個(gè)線程訪問某個(gè)特定資源的問題,不管是是互斥鎖還是信號(hào)量,都沒法解決另外一個(gè)問題:鎖定的共享資源會(huì)引起讀寫問題,大多數(shù)情況下,限制資源一次只能有一個(gè)線程進(jìn)行讀取訪問其實(shí)是非常浪費(fèi)的。所以就引出了GCD 的barrier來(lái)解決這個(gè)問題(資源饑餓)。
GCD barrier 解決資源饑餓 問題

dispatch_barrier使用前提:一定要在一個(gè)隊(duì)列里面(自定義并行隊(duì)列)。
不能用global queue,因?yàn)檫@個(gè)全局隊(duì)列,每次系統(tǒng)分配可能在不同的隊(duì)列里面,比如你的barrier在隊(duì)列A里面,流程走到這,執(zhí)行自己barrier任務(wù),沒用,后面的任務(wù)在其他隊(duì)列做了,不等barrier任務(wù),所以barrier根本不起效果。
剩下就是自定義一個(gè)隊(duì)列,分串行隊(duì)列,和并行隊(duì)列,如果是串行隊(duì)列,就不用了barrier了,他本身就是按照順序來(lái)執(zhí)行的,加barrier攔截沒有意義,所以只能用自定義隊(duì)列的并行方式。
concurrentQueue =dispatch_queue_create("www.test.com", DISPATCH_QUEUE_CONCURRENT);
- (NSString *)someString{
? ? ? ?__weak NSString *localSomeString; //1
? ? ? ?dispatch_sync(concurrentQueue, ^{
? ? ? ? ? ? ? localSomeString = _someString; //2
? ? ? ? });
? ? ? return localSomeString; //3
}
- (void)setSomeString:(NSString *)someString{
? ? ? ?// barrier
? ? ? ?dispatch_barrier_async(concurrentQueue, ^{
? ? ? ? ? ? ?_someString = someString;
? ? ? ?});
}
//當(dāng)使用并發(fā)隊(duì)列時(shí),要確保所有的 barrier 調(diào)用都是 async 的,如果你使用 dispatch_barrier_sync ,那么你很可能會(huì)使你自己(更確切的說是,你的代碼)產(chǎn)生死鎖。
分析:寫操作 ,只能用barrier,保證多線程寫的安全,讀取操作,可以是并行,
dispatch_sync(concurrentQueue, ^{localSomeString = _someString;});
return localSomeString;
這個(gè)操作是把block內(nèi)容放到并行隊(duì)列里面,這里用dispatch_sync原因就是后面還需要? ? return localSomeString;也就是必須讓他等著,不能block里面賦值還沒做,就直接返回了,所以要用dispatch_sync,流程就成了,下面的1,2,3符合邏輯。這里雖然是同步執(zhí)行,但是任務(wù)是放在并行隊(duì)列里面的,所以任務(wù)還是并行執(zhí)行的。當(dāng)然,如果這里不是讀取操作,是其他操作,不需要后面的返回值,就可以用dispatch_async,異步執(zhí)行,所以需要看你具體做的事情。
其他:
并行: 自定義并行隊(duì)列 ?、 全局隊(duì)列 ?==>放在里面的任務(wù)block ,是可以同步執(zhí)行的
串行: 自定義串行隊(duì)列 ?、 ?主線程 ?==> 放在里面的任務(wù)block,是一個(gè)挨著一個(gè),按照順序來(lái)執(zhí)行的。
同步:dispatch_sync ? ==> ?就在當(dāng)前線程做事情
異步:dispatch_async? ==> ?會(huì)開一個(gè)新線程 做事情