iOS多線程 ---線程安全

背景:在多線程中属铁,如果同時搶一塊資源眠寿,比如給數(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? ==> ?會開一個新線程 做事情

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫉称,一起剝皮案震驚了整個濱河市侦镇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澎埠,老刑警劉巖虽缕,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒲稳,居然都是意外死亡,警方通過查閱死者的電腦和手機伍派,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門江耀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诉植,你說我怎么就攤上這事祥国。” “怎么了晾腔?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵舌稀,是天一觀的道長。 經(jīng)常有香客問我灼擂,道長壁查,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任剔应,我火速辦了婚禮睡腿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峻贮。我一直安慰自己席怪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布纤控。 她就那樣靜靜地躺著挂捻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪船万。 梳的紋絲不亂的頭發(fā)上刻撒,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天惜辑,我揣著相機與錄音,去河邊找鬼疫赎。 笑死盛撑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捧搞。 我是一名探鬼主播抵卫,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胎撇!你這毒婦竟也來了介粘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤晚树,失蹤者是張志新(化名)和其女友劉穎姻采,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爵憎,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡慨亲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宝鼓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刑棵。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖愚铡,靈堂內(nèi)的尸體忽然破棺而出蛉签,到底是詐尸還是另有隱情,我是刑警寧澤沥寥,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布碍舍,位于F島的核電站,受9級特大地震影響邑雅,放射性物質(zhì)發(fā)生泄漏片橡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一蒂阱、第九天 我趴在偏房一處隱蔽的房頂上張望锻全。 院中可真熱鬧,春花似錦录煤、人聲如沸鳄厌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽了嚎。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歪泳,已是汗流浹背萝勤。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呐伞,地道東北人敌卓。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像伶氢,于是被迫代替她去往敵國和親趟径。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容