iOS的線程安全與鎖

Cat.png

在iOS編碼中,鎖的出現(xiàn)其實是因為多線程會出現(xiàn)線程安全的問題镊靴。那么铣卡,問題來了,什么是線程安全偏竟?為什么鎖可以解決線程安全問題煮落?單線程是不是絕對的線程安全?iOS編程有多少種鎖踊谋?加解鎖的效率如何蝉仇?......

一、什么是線程安全?

WIKI: Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfil their design specifications without unintended interaction.

用人話來說:多線程操作共享數(shù)據(jù)不會出現(xiàn)想不到的結果就是線程安全的轿衔,否則沉迹,是線程不安全的。

舉個例子:

NSInteger total = 0;
- (void)threadNotSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
        });
    }
}

//第一次輸出:
2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 1
2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3
2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2
2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2
2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1
2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0

//第二次輸出
2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1
2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2
2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3
2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2
2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1
2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0

NSInteger total = 0;
NSLock *lock = [NSLock new];
- (void)threadSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [lock lock];
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
            [lock unlock];
        });
    }
}

//第一次輸出
2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 1
2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0
2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1
2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0
2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1
2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0

//第二次輸出
2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1
2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0
2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1
2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0
2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1
2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0

第一個函數(shù)第一次和第二次調用的結果不一樣害驹,換句話說鞭呕,不能確定代碼的運行順序和結果,是線程不安全的宛官;第二個函數(shù)第一次和第二次輸出結果一樣葫松,可以確定函數(shù)的執(zhí)行結果,是線程安全的底洗。
居于線程安全的含義腋么,知道線程安全是相對于多線程而言的,單線程不會存在線程安全問題亥揖。因為珊擂,單線程代碼的執(zhí)行順序是確定的,可以知道代碼的執(zhí)行結果徐块。

二未玻、鎖鎖鎖??

Lock.png

線程不安全是由于多線程訪問造成的,那么如何解決胡控?
1.既然線程安全問題是由多線程引起的扳剿,那么,最極端的可以使用單線程保證線程安全昼激。
2.線程安全是由于多線程訪問和修改共享資源而引起不可預測的結果庇绽,因此,如果都是訪問共享資源而不去修改共享資源也可以保證線程安全橙困,比如:設置只讀屬性的全局變量瞧掺。
3.使用鎖。

引用 ibireme《不再安全的 OSSpinLock》中的一張圖片說明加解鎖的效率:

Locks.png

我也下載了 ibiremeGitHub 上面的Demo來跑過(環(huán)境 iPhone6 iOS11.1)凡傅。發(fā)現(xiàn)辟狈,不同的循環(huán)次數(shù),結果都不一樣夏跷,并沒有得到和 ibireme 一樣的結果哼转。所以,上面的柱狀圖也只做一個定向分析槽华,并不是很準確的結果壹蔓。

下面會對這些鎖的實現(xiàn)原理和用法做簡單的總結和處理(詳細的實現(xiàn)原理,可以看 bestswift 的這篇文章《深入理解 iOS 開發(fā)中的鎖》):

OSSpinLock:

自旋鎖的實現(xiàn)原理比較簡單猫态,就是死循環(huán)佣蓉。當a線程獲得鎖以后披摄,b線程想要獲取鎖就需要等待a線程釋放鎖。在沒有獲得鎖的期間勇凭,b線程會一直處于忙等的狀態(tài)疚膊。如果a線程在臨界區(qū)的執(zhí)行時間過長,則b線程會消耗大量的cpu時間套像,不太劃算酿联。所以,自旋鎖用在臨界區(qū)執(zhí)行時間比較短的環(huán)境性能會很高夺巩。

自旋鎖的代碼實現(xiàn):

#import <libkern/OSAtomic.h>

OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
//需要執(zhí)行的代碼
OSSpinLockUnlock(&lock);

//OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock)
//蘋果在OSSpinLock注釋表示被廢棄,改用不安全的鎖替代
dispatch_semaphore:

dispatch_semaphore實現(xiàn)的原理和自旋鎖有點不一樣周崭。首先會先將信號量減一柳譬,并判斷是否大于等于0,如果是续镇,則返回0美澳,并繼續(xù)執(zhí)行后續(xù)代碼,否則摸航,使線程進入睡眠狀態(tài)制跟,讓出cpu時間。直到信號量大于0或者超時酱虎,則線程會被重新喚醒執(zhí)行后續(xù)操作雨膨。

dispatch_semaphore_t lock = dispatch_semaphore_create(1);    //傳入的參數(shù)必須大于或者等于0,否則會返回Null
long wait = dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);    //wait = 0读串,則表示不需要等待聊记,直接執(zhí)行后續(xù)代碼;wait != 0恢暖,則表示需要等待信號或者超時排监,才能繼續(xù)執(zhí)行后續(xù)代碼。lock信號量減一杰捂,判斷是否大于0舆床,如果大于0則繼續(xù)執(zhí)行后續(xù)代碼;lock信號量減一少于或者等于0嫁佳,則等待信號量或者超時挨队。
//需要執(zhí)行的代碼
long signal = dispatch_semaphore_signal(lock);    //signal = 0,則表示沒有線程需要其處理的信號量脱拼,換句話說瞒瘸,沒有需要喚醒的線程;signal != 0熄浓,則表示有一個或者多個線程需要喚醒情臭,則喚醒一個線程省撑。(如果線程有優(yōu)先級,則喚醒優(yōu)先級最高的線程俯在,否則竟秫,隨機喚醒一個線程。)
pthread_mutex:

pthread_mutex表示互斥鎖跷乐,和信號量的實現(xiàn)原理類似肥败,也是阻塞線程并進入睡眠,需要進行上下文切換愕提。

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    
pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);    //設置屬性
    
pthread_mutex_lock(&lock);    //上鎖
//需要執(zhí)行的代碼
pthread_mutex_unlock(&lock);    //解鎖
NSLock:

NSLock在內部封裝了一個 pthread_mutex馒稍,屬性為 PTHREAD_MUTEX_ERRORCHECK

NSLock *lock = [NSLock new];
[lock lock];
//需要執(zhí)行的代碼
[lock unlock];
NSCondition:

NSCondition封裝了一個互斥鎖和條件變量浅侨∨耍互斥鎖保證線程安全,條件變量保證執(zhí)行順序如输。

NSCondition *lock = [NSCondition new];
[lock lock];
//需要執(zhí)行的代碼
[lock unlock];
pthread_mutex(recursive):

pthread_mutex鎖的一種鼓黔,屬于遞歸鎖。一般一個線程只能申請一把鎖不见,但是澳化,如果是遞歸鎖,則可以申請很多把鎖稳吮,只要上鎖和解鎖的操作數(shù)量就不會報錯缎谷。

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);    //設置屬性
    
pthread_mutex_lock(&lock);    //上鎖
//需要執(zhí)行的代碼
pthread_mutex_unlock(&lock);    //解鎖
NSRecursiveLock:

遞歸鎖,pthread_mutex(recursive)的封裝盖高。

NSRecursiveLock *lock = [NSRecursiveLock new];
[lock lock];
//需要執(zhí)行的代碼
[lock unlock];
NSConditionLock:

NSConditionLock借助 NSCondition 來實現(xiàn)慎陵,本質是生產者-消費者模型。

NSConditionLock *lock = [NSConditionLock new];
[lock lock];
//需要執(zhí)行的代碼
[lock unlock];
@synchronized:

一個對象層面的鎖喻奥,鎖住了整個對象席纽,底層使用了互斥遞歸鎖來實現(xiàn)。

NSObject *object = [NSObject new];
@synchronized(object) {
  //需要執(zhí)行的代碼
}

三撞蚕、總結

這里只是一些簡單的總結润梯,更多深入的研究請自行 Google

參考:

深入理解 iOS 開發(fā)中的鎖
不再安全的 OSSpinLock
Threading Programming Guide
關于 @synchronized甥厦,這兒比你想知道的還要多
OS中保證線程安全的幾種方式與性能對比
iOS 常見知識點(三):Lock
iOS多線程到底不安全在哪里纺铭?

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刀疙,隨后出現(xiàn)的幾起案子舶赔,更是在濱河造成了極大的恐慌,老刑警劉巖谦秧,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟纳,死亡現(xiàn)場離奇詭異撵溃,居然都是意外死亡,警方通過查閱死者的電腦和手機锥累,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門缘挑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桶略,你說我怎么就攤上這事语淘。” “怎么了际歼?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵惶翻,是天一觀的道長。 經(jīng)常有香客問我鹅心,道長维贺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任巴帮,我火速辦了婚禮,結果婚禮上虐秋,老公的妹妹穿的比我還像新娘榕茧。我一直安慰自己,他們只是感情好客给,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布用押。 她就那樣靜靜地躺著,像睡著了一般靶剑。 火紅的嫁衣襯著肌膚如雪蜻拨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天桩引,我揣著相機與錄音缎讼,去河邊找鬼。 笑死坑匠,一個胖子當著我的面吹牛血崭,可吹牛的內容都是我干的。 我是一名探鬼主播厘灼,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼夹纫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了设凹?” 一聲冷哼從身側響起舰讹,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闪朱,沒想到半個月后月匣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钻洒,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年桶错,在試婚紗的時候發(fā)現(xiàn)自己被綠了航唆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡院刁,死狀恐怖糯钙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情退腥,我是刑警寧澤任岸,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站狡刘,受9級特大地震影響享潜,放射性物質發(fā)生泄漏。R本人自食惡果不足惜嗅蔬,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一剑按、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澜术,春花似錦艺蝴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盒延,卻和暖如春缩擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背添寺。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工胯盯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畦贸。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓陨闹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親薄坏。 傳聞我的和親對象是個殘疾皇子趋厉,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容

  • iOS線程安全的鎖與性能對比 一、鎖的基本使用方法 1.1胶坠、@synchronized 這是我們最熟悉的枷鎖方式君账,...
    Jacky_Yang閱讀 2,224評論 0 17
  • 前言 在多線程開發(fā)中,常會遇到多個線程訪問修改數(shù)據(jù)沈善。為了防止數(shù)據(jù)不一致或數(shù)據(jù)污染乡数,通常采用加鎖機制來保證線程安全椭蹄。...
    趙夢楠閱讀 946評論 0 5
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝,對鎖的使用很少净赴,剛好之前面試中被問到的關于并發(fā)編程鎖的問題绳矩,都是一知...
    喵渣渣閱讀 3,707評論 0 33
  • 線程安全是怎么產生的 常見比如線程內操作了一個線程外的非線程安全變量,這個時候一定要考慮線程安全和同步玖翅。 - (v...
    幽城88閱讀 665評論 0 0
  • 原文鏈接 翻譯者:倪辰皓 最后翻譯時間:2017/06/18 目前狀態(tài):已完成 轉載請附上本文鏈接: http:/...
    即墨燈火閱讀 1,172評論 0 4