iOS 多線程(一)-鎖

1.概述
1.1 類型
  • 自旋鎖 OSSpinLock
  • 信號量 dispatch_semaphore
  • 互斥鎖 pthread_mutex夯尽、NSLock
  • 遞歸鎖 pthread_mutex(recursive)脑漫、NSRecursiveLock、@sychronized
  • 條件鎖 NSCondition咕幻、NSConditionLock

參考一張著名的圖:


image

他們的性能從上到下依次降低

1.2 基礎原理
  • 什么是鎖?通過一種機制,將代碼片段劃分成一段一段臨界區(qū)域翁狐,確保同一時間只有一個臨界區(qū)域處于運行狀態(tài)钥飞;鎖主要解決線程同步問題,避免引發(fā)數(shù)據(jù)混亂等未知問題
  • 互斥鎖 也叫同步鎖
  • 遞歸鎖 其實也屬于互斥鎖的一種衫嵌,對于互斥鎖读宙,當同一個線程多次訪問同一個鎖時會引發(fā)死鎖;而遞歸鎖不會出現(xiàn)此問題楔绞,遞歸鎖的例子结闸,參考Reentrant_mutex#Example

    Thread A calls function F which acquires a reentrant lock for itself before proceeding
    Thread B calls function F which attempts to acquire a reentrant lock for itself but cannot due to one already outstanding, resulting in either a block (it waits), or a timeout if requested
    Thread A's F calls itself recursively. It already owns the lock, so it will not block itself (no deadlock). This is the central idea of a reentrant mutex, and is what makes it different from a regular lock.
    Thread B's F is still waiting, or has caught the timeout and worked around it
    Thread A's F finishes and releases its lock(s)
    Thread B's F can now acquire a reentrant lock and proceed if it was still waiting

理解:當一個線程調用F加鎖之后唇兑,若F中又調用自己,就不再加鎖了桦锄,所以不會死鎖

  • 自旋鎖 是一種內核級別的鎖扎附, 采用忙等機制加鎖,OSSpinLock的頭文件是<libkern/OSAtomic.h>结耀,該鎖在iOS10以上廢棄了留夜,改為os_unfair_lock_t
2. 實現(xiàn)原理

參考:探討iOS開發(fā)中的各種鎖

2.1 原理概述
  • 這么多種鎖其實大的實現(xiàn)思路就兩種,一種叫忙等待图甜,一種叫阻塞
  • 所謂忙等待就是碍粥,讓一個線程處于一直處于等待狀態(tài),直至鎖釋放黑毅;阻塞是讓一個線程掛起嚼摩,直至鎖釋放,操作系統(tǒng)會切換上下文到另個線程矿瘦;相比而言枕面,忙等待機制會更耗CPU資源,因為線程還在跑

現(xiàn)在互斥鎖大多使用隊列和上下文切換以達到節(jié)約資源和降低延遲的目的缚去;但總有情況潮秘,掛起一個線程,然后切換上下文再恢復的時間比線程忙等所用的時間長病游,所以這時候就要用自旋鎖唇跨。參考:互斥鎖

  • 使用鎖會出現(xiàn)優(yōu)先級倒置(高優(yōu)先級線程會等待地優(yōu)先級線程執(zhí)行,下邊OSSpinLock自旋鎖就是衬衬,有詳細解釋)买猖、資源饑荒(某個線程一直得不到足夠的鎖資源)的情況
2.2 各種鎖的原理
  • OSSpinLock 是一種忙等機制,利用全局變量表示鎖的狀態(tài)滋尉,它會存在優(yōu)先級反轉問題玉控,就是說當高優(yōu)先級通過信號機制訪問低優(yōu)先級,信號量已經(jīng)被低優(yōu)先級占有狮惜,導致高優(yōu)先級任務被低優(yōu)先級任務所阻塞
  • dispatch_semaphore 信號量機制不僅能操作線程高诺,其實還能夠操作進程,只是iOS 單個應用來說是單進程的(此句有誤碾篡,UI渲染就在一個新線程叫backbond)虱而;當wait時,信號量的值-1开泽;當signal時牡拇,信號量的值+1,當信號量的值==0時,當前線程會被阻塞惠呼;所以初始值應該傳入1
  • pthread_mutex 互斥鎖导俘,是一套跨平臺的API,采用的阻塞機制
  • pthread_mutex(recursive) 與pthread_mutex類似
  • NSLock 一種互斥鎖剔蹋,內部封裝的 pthread_mutex實現(xiàn)
  • NSRecursiveLock 與NSLock類似旅薄,內部封裝的pthread_mutex實現(xiàn),鎖的類型不同PTHREAD_MUTEX_RECURSIVE
  • NSCondition 封裝了一個互斥鎖和條件變量
  • NSConditionLock 以此來實現(xiàn)(以上兩個沒有用過泣崩,沒有發(fā)言權暫不探究了)
  • @sychronized 這個性能最慢的鎖少梁,但用起來也最爽,它的原理是需要深入了解的(凡是封裝的較深的是有必要探究下的)

NSLock是pthread_mutex的封裝可以通用
NSRecursiveLock是pthread_mutex(recursive) 的封裝

2.3 @sychronized原理

參考:關于 @synchronized律想,這兒比你想知道的還要多
具體探究過程請參考文章猎莲,結合源碼閱讀即可
一句話總結:

編譯器會將{}代碼放入try{}finally{}中,在{開始時技即,會轉化成objc_sync_enter方法,內部調用pthread_mutex創(chuàng)建一個遞歸鎖樟遣,并分配給@sychronized(obj)傳入的obj而叼;在}結束時調用objc_sync_exit方法釋放分配的鎖

偽代碼如下:

NSString *test = @"test";
id synchronizeTarget = (id)test;
@try {
    objc_sync_enter(synchronizeTarget);
    test = nil;
} @finally {
    objc_sync_exit(synchronizeTarget);   
}

理解:

  1. 分配的鎖通過對象的內存地址與對象建立關聯(lián)
  2. 若傳入nil對象,不會出問題豹悬,但等于沒有加鎖
  3. 若在{}中對象置為nil葵陵,也不會出現(xiàn)問題,編譯器做了處理瞻佛,即保證enter和exit方法傳入的對象是同一個
  4. @sychronized不會修改對象的引用計數(shù)
3. 使用場景

目前用過兩種脱篙,一種是互斥鎖,一種是信號量伤柄,平時開發(fā)中把這兩個掌握好基本就能滿足大部分需求绊困,從一些知名的開源庫中尋找使用場景:
3.1 在ReactiveCocoa 中大量使用OSSpinLock,來保證大量全局變量的同步适刀,一般這樣用
eg: 在RACCompoundDisposable.m中

- (BOOL)isDisposed {
    OSSpinLockLock(&_spinLock);
    BOOL disposed = _disposed;
    OSSpinLockUnlock(&_spinLock);

    return disposed;
}

3.2 在AFNetworking秤朗、ReactiveCocoa中可以看到NSLock
eg:在AFURLResponseSerializer.m中,可以看到從NSData轉UIImage時使用了同步鎖笔喉,解決[UIImage imageWithData:data]線程不安全的問題取视,參考issue #2572 Crash on AFImageWithDataAtScale when loading image

+ (UIImage *)af_safeImageWithData:(NSData *)data {
    UIImage* image = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        imageLock = [[NSLock alloc] init];
    });
    
    [imageLock lock];
    image = [UIImage imageWithData:data]; //該方法不是線程安全的
    [imageLock unlock];
    return image;
}

3.3 在CocoaLumberjack中大量使用pthread_mutex來保證線程同步,在DDContextFilterLogFormatter.m一個例子:

- (NSArray *)currentSet {
    NSArray *result = nil;

    pthread_mutex_lock(&_mutex);
    {
        result = [_set allObjects];
    }
    pthread_mutex_unlock(&_mutex);

    return result;
}

3.4 在AFURLSessionManager.m tasksForKeyPath方法使用dispatch_semaphore_t來阻塞當前線程常挚,例子如下:

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //當getTasksWithCompletionHandler執(zhí)行完畢signal時時作谭,才會該函數(shù)才會return,是一種保證函數(shù)返回值同步的好辦法

    return tasks;
}

小總結:

  1. 使用最多的是NSLock這種形式奄毡、其次是pthread_mutex和信號量
  2. 盡量不要使用@sychronized這種方式折欠,因為它性能較低
4. 補充

多線程中棧是私有的,堆是公有的
每個線程擁有一個棧和計數(shù)器,棧和計數(shù)器用來保存線程的執(zhí)行歷史和執(zhí)行狀態(tài)怨酝;
其他資源(堆傀缩、地址空間、全局變量)是同一個進程內多個線程共享

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末农猬,一起剝皮案震驚了整個濱河市赡艰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斤葱,老刑警劉巖慷垮,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揍堕,居然都是意外死亡料身,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門衩茸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芹血,“玉大人,你說我怎么就攤上這事楞慈♂V颍” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵囊蓝,是天一觀的道長饿悬。 經(jīng)常有香客問我,道長聚霜,這世上最難降的妖魔是什么狡恬? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蝎宇,結果婚禮上弟劲,老公的妹妹穿的比我還像新娘。我一直安慰自己夫啊,他們只是感情好函卒,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撇眯,像睡著了一般报嵌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熊榛,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天锚国,我揣著相機與錄音,去河邊找鬼玄坦。 笑死血筑,一個胖子當著我的面吹牛绘沉,可吹牛的內容都是我干的。 我是一名探鬼主播豺总,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼车伞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喻喳?” 一聲冷哼從身側響起另玖,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤吉嫩,失蹤者是張志新(化名)和其女友劉穎鸠真,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體外傅,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蹦哼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年鳄哭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纲熏。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡妆丘,死狀恐怖,靈堂內的尸體忽然破棺而出局劲,到底是詐尸還是另有隱情飘痛,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布容握,位于F島的核電站,受9級特大地震影響车柠,放射性物質發(fā)生泄漏剔氏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一竹祷、第九天 我趴在偏房一處隱蔽的房頂上張望谈跛。 院中可真熱鬧,春花似錦塑陵、人聲如沸感憾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阻桅。三九已至,卻和暖如春兼都,著一層夾襖步出監(jiān)牢的瞬間嫂沉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工扮碧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趟章,地道東北人杏糙。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像蚓土,于是被迫代替她去往敵國和親宏侍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內容

  • 線程安全是怎么產(chǎn)生的 常見比如線程內操作了一個線程外的非線程安全變量蜀漆,這個時候一定要考慮線程安全和同步谅河。 - (v...
    幽城88閱讀 656評論 0 0
  • demo下載 建議一邊看文章,一邊看代碼嗜愈。 聲明:關于性能的分析是基于我的測試代碼來的旧蛾,我也看到和網(wǎng)上很多測試結果...
    炸街程序猿閱讀 789評論 0 2
  • 多線程需要一種互斥的機制來訪問共享資源。 一蠕嫁、 互斥鎖 互斥鎖的意思是某一時刻只允許一個線程訪問某一資源锨天。為了保證...
    doudo閱讀 721評論 0 5
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝,對鎖的使用很少剃毒,剛好之前面試中被問到的關于并發(fā)編程鎖的問題病袄,都是一知...
    喵渣渣閱讀 3,690評論 0 33
  • iOS線程安全的鎖與性能對比 一、鎖的基本使用方法 1.1赘阀、@synchronized 這是我們最熟悉的枷鎖方式益缠,...
    Jacky_Yang閱讀 2,212評論 0 17