iOS多線程 ---線程安全

背景:在多線程中,如果同時(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è)新線程 做事情

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容