背景:在多線程中属铁,如果同時搶一塊資源眠寿,比如給數(shù)組追加數(shù)據(jù),可變數(shù)組(字典)不是線程安全的红选,就會導(dǎo)致crash澜公,為了避免這種問題,就衍生出線程同步喇肋,來保證線程安全坟乾。
先介紹幾種鎖,
第一種鎖
@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)識蝶防,@synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識甚侣,只有當(dāng)標(biāo)識相同時,才為滿足互斥间学,如果線程2中的@synchronized(obj)改為@synchronized(self),剛線程2就不會被阻塞殷费,@synchronized塊會隱式的添加一個異常處理例程來保護代碼印荔,該處理例程會在異常拋出的時候自動的釋放互斥鎖。
2.synchronized 優(yōu)劣分析:
劣勢:@synchronized塊會隱式的添加一個異常處理例程來保護代碼详羡,該處理例程會在異常拋出的時候自動的釋放互斥鎖仍律。開銷大,性能差实柠,不適合在生成環(huán)境使用水泉。
優(yōu)點:使用@synchronized關(guān)鍵字可以很方便地創(chuàng)建鎖對象,而且不用顯式的創(chuàng)建鎖對象窒盐。
3.synchronized 內(nèi)部的鎖是一個遞歸鎖草则。
4.atomic:原子屬性,為setter方法加鎖蟹漓,的內(nèi)部實現(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];而不是放在最后葡粒,就加鎖份殿,解鎖就一一對應(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];
});
//簡單分析:這段代碼是一個典型的死鎖情況塔鳍。在我們的線程中1是遞歸調(diào)用的伯铣。所以每次進入這個block時,都會去加一次鎖轮纫,而從第二次開始腔寡,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除掌唾,這樣就導(dǎo)致了死鎖放前,
//調(diào)試器有這些提示:[NSLock lock]: deadlock
//如果在在遞歸或循環(huán)中正確的使用鎖:
NSRecursiveLock 遞歸鎖,直接NSLock *theLock = [[NSLock alloc] init];
替換成NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];就ok了
第三種鎖
NSConditionLock條件鎖
特殊場景使用糯彬,也就是滿足一定的條件下凭语,才解鎖,和創(chuàng)建鎖撩扒。
第四種方式似扔,用信號量來實現(xiàn)鎖的功能
信號量 ==>當(dāng)信號個數(shù)為 0 時,則線程阻塞搓谆,等待發(fā)送新信號炒辉;一旦信號個數(shù)大于 0 時,就開始處理任務(wù)泉手。
dispatch_semaphore_create黔寇、
dispatch_semaphore_wait? //在執(zhí)行完這行代碼后,信號量就會減一斩萌,如果信號量變成了0缝裤,那么就在這里等著屏轰,后面一般就是一些排他操作(比如:數(shù)組的寫操作,保證同步)
dispatch_semaphore_signal憋飞、 //作用:在排他操作結(jié)束后霎苗,現(xiàn)在就可以讓其他線程來玩了,執(zhí)行完這行代碼搀崭,信號量加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);有個超時時間瘤睹,一般可以寫作DISPATCH_TIME_FOREVER,永不超時答倡。
//這里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é)一下:
互斥鎖特點:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了获茬,線程會進入休眠狀態(tài)等待鎖。一旦被訪問的資源被解鎖倔既,則等待資源的線程會被喚醒恕曲。
自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會以死循環(huán)的方式等待鎖渤涌,一旦被訪問的資源被解鎖佩谣,則等待資源的線程會立即執(zhí)行。性能最高的鎖实蓬,OSSpinLock已經(jīng)不再安全茸俭,不再使用。
信號量:一個線程完成了操作完共享數(shù)據(jù)后安皱,就通過發(fā)出信號量告訴別的線程调鬓,你們現(xiàn)在可以用這塊共享資源了。
性能比較:OSSpinLock > dispatch_semaphore > NSLock > NSRecursiveLock > NSConditionLock > @synchronized.
生產(chǎn)環(huán)境一般推薦使用dispatch_semaphore酌伊。
上述都是解決同一時刻腾窝,只允許一個線程訪問某個特定資源的問題,不管是是互斥鎖還是信號量居砖,都沒法解決另外一個問題:鎖定的共享資源會引起讀寫問題虹脯,大多數(shù)情況下,限制資源一次只能有一個線程進行讀取訪問其實是非常浪費的悯蝉。所以就引出了GCD 的barrier來解決這個問題(資源饑餓)归形。
GCD barrier 解決資源饑餓 問題
dispatch_barrier使用前提:一定要在一個隊列里面(自定義并行隊列)。
不能用global queue,因為這個全局隊列鼻由,每次系統(tǒng)分配可能在不同的隊列里面暇榴,比如你的barrier在隊列A里面厚棵,流程走到這,執(zhí)行自己barrier任務(wù)蔼紧,沒用婆硬,后面的任務(wù)在其他隊列做了,不等barrier任務(wù)奸例,所以barrier根本不起效果彬犯。
剩下就是自定義一個隊列,分串行隊列查吊,和并行隊列谐区,如果是串行隊列,就不用了barrier了逻卖,他本身就是按照順序來執(zhí)行的宋列,加barrier攔截沒有意義,所以只能用自定義隊列的并行方式评也。
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ā)隊列時炼杖,要確保所有的 barrier 調(diào)用都是 async 的,如果你使用 dispatch_barrier_sync ,那么你很可能會使你自己(更確切的說是盗迟,你的代碼)產(chǎn)生死鎖坤邪。
分析:寫操作 ,只能用barrier罚缕,保證多線程寫的安全艇纺,讀取操作,可以是并行怕磨,
dispatch_sync(concurrentQueue, ^{localSomeString = _someString;});
return localSomeString;
這個操作是把block內(nèi)容放到并行隊列里面喂饥,這里用dispatch_sync原因就是后面還需要? ? return localSomeString;也就是必須讓他等著,不能block里面賦值還沒做肠鲫,就直接返回了员帮,所以要用dispatch_sync,流程就成了导饲,下面的1捞高,2,3符合邏輯渣锦。這里雖然是同步執(zhí)行吏够,但是任務(wù)是放在并行隊列里面的涝涤,所以任務(wù)還是并行執(zhí)行的默垄。當(dāng)然砸烦,如果這里不是讀取操作,是其他操作听盖,不需要后面的返回值胀溺,就可以用dispatch_async,異步執(zhí)行裂七,所以需要看你具體做的事情。
其他:
并行: 自定義并行隊列 ?仓坞、 全局隊列 ?==>放在里面的任務(wù)block 背零,是可以同步執(zhí)行的
串行: 自定義串行隊列 ?、 ?主線程 ?==> 放在里面的任務(wù)block无埃,是一個挨著一個徙瓶,按照順序來執(zhí)行的。
同步:dispatch_sync ? ==> ?就在當(dāng)前線程做事情
異步:dispatch_async? ==> ?會開一個新線程 做事情