多線程編程之線程同步

各種鎖的性能.png
線程安全是怎么產(chǎn)生的

每個線程都擁有它自己的執(zhí)行堆棧酷勺,由內(nèi)核調(diào)度獨立的運行時間片。一個線程可 以和其他線程或其他進程通信扳躬,執(zhí)行 I/O 操作脆诉,甚至執(zhí)行任何你想要它完成的任務(wù)。 因為它們處于相同的進程空間贷币,所以一個獨立應(yīng)用程序里面的所有線程共享相同的虛 擬內(nèi)存空間击胜,并且具有和進程相同的訪問權(quán)限。線程編程的危害之一是在多個線程之間的資源爭奪役纹。如果多個線程在同一個時間 試圖使用或者修改同一個資源偶摔,就會出現(xiàn)問題。緩解該問題的方法之一是消除共享資源促脉。

同步工具

為了防止不同線程意外修改數(shù)據(jù)辰斋,你可以設(shè)計你的程序沒有同步問題,或你也可 以使用同步工具瘸味。盡管完全避免出現(xiàn)同步問題相對更好一點宫仗,但是幾乎總是無法實現(xiàn)。 以下個部分介紹了你可以使用的同步工具的基本類別旁仿。

1.原子操作(atomic

我們在聲明一個變量的時候一般會使用nonatomic藕夫,這個就是非原子操作;原子操作是atomic枯冈。
簡單的加減使用原子操作具有更高的性能優(yōu)勢汁胆。注意是加減,不是增刪K住嫩码!
也就是說僅僅對于getter,setter是線程安全的,兩個線程都去對變量賦值是安全的罪既。對于比如NSMutableArray類型的增刪操作不是線程安全的

2.內(nèi)存屏障和 Volatile變量

因為內(nèi)存屏障和 volatile變量降低了編譯器可執(zhí)行的優(yōu)化铸题,因此你應(yīng)該謹慎使 用它們铡恕,只在有需要的地方時候,以確保正確性

3.鎖Lock

鎖是最常用的同步工具丢间。你可以是使用鎖來保護臨界區(qū)(critical section)探熔,這 些代碼段在同一個時間只能允許被一個線程訪問。比如烘挫,一個臨界區(qū)可能會操作一個 特定的數(shù)據(jù)結(jié)構(gòu)诀艰,或使用了每次只能一個客戶端訪問的資源

3.1 信號量 Dispatch Semaphore

我的上一篇文章中有講過一次性搞懂 GCD的所有用法

使用dispatch_semaphore_signal加1 dispatch_semaphore_wait減1,為0時等待的設(shè)置方式來達到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題饮六。

  • dispatch_semaphore_create(long value)
  • dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema)
  • dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout)
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程1 等待ing");
    dispatch_semaphore_wait(signal, overTime); //signal 值 -1
    NSLog(@"線程1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"線程1 發(fā)送信號");
    NSLog(@"--------------------------------------------------------");
});

//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"線程2 等待ing");
    dispatch_semaphore_wait(signal, overTime);
    NSLog(@"線程2");
    dispatch_semaphore_signal(signal);
    NSLog(@"線程2 發(fā)送信號");
});

關(guān)于信號量其垄,我們可以用停車來比喻:

停車場剩余4個車位,那么即使同時來了四輛車也能停的下卤橄。如果此時來了五輛車绿满,那么就有一輛需要等待。
信號量的值(signal)就相當于剩余車位的數(shù)目窟扑,dispatch_semaphore_wait 函數(shù)就相當于來了一輛車喇颁,dispatch_semaphore_signal 就相當于走了一輛車。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value))嚎货,調(diào)用一次dispatch_semaphore_signal橘霎,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait 剩余車位就減少一個殖属;當剩余車位為 0 時茎毁,再來車(即調(diào)用 dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位忱辅。有些車主沒有耐心七蜘,給自己設(shè)定了一段等待時間,這段時間內(nèi)等不到停車位就走了墙懂,如果等到了就開進去停車橡卤。而有些車主就像把車停在這,所以就一直等下去损搬。

3.2 POSIX 互斥鎖

POSIX互斥鎖在程序里面很容易使用碧库。

  • 導入頭文件 #import <pthread.h>
  • 聲明并 pthread_mutex_t mutex,
  • 初始化 pthread_mutex_init(&mutex, NULL)
  • pthread_mutex_lockpthread_mutex_unlock函數(shù), 進行加鎖解鎖 * pthread_mutex_destroy釋放該鎖的數(shù)據(jù)結(jié)構(gòu)。
#import <pthread.h>
@interface MYPOSIXViewController ()
{
    /** 聲明pthread_mutex_t的結(jié)構(gòu) */
    pthread_mutex_t mutex; 
}
@end

@implementation MYPOSIXViewController
- (void)dealloc{
    /** 釋放該鎖的數(shù)據(jù)結(jié)構(gòu) */
    pthread_mutex_destroy(&mutex);  
}
- (void)viewDidLoad {
    [super viewDidLoad];
   /** 初始化 */
    pthread_mutex_init(&mutex, NULL);
}

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    /**  加鎖 */
    pthread_mutex_lock(&mutex);
    if (imageNames.count>0) {
        imageName = [imageNames firstObject];
        [imageNames removeObjectAtIndex:0];
    }
    /** 解鎖 */
    pthread_mutex_unlock(&mutex);
}
3.3 NSLock互斥鎖

在 Cocoa 程序中 NSLock 中實現(xiàn)了一個簡單的互斥鎖巧勤。所有鎖(包括 NSLock)的 接口實際上都是通過NSLocking協(xié)議定義的嵌灰,它定義了 lockunlock方法。你使用 這些方法來獲取和釋放該鎖颅悉。
除了標準的鎖行為沽瞭,NSLock 類還增加了tryLocklockBeforeDate:方法。方法 tryLock 試圖獲取一個鎖剩瓶,但是如果鎖不可用的時候驹溃,它不會阻塞線程城丧。相反,它只 是返回 NO豌鹤。而 lockBeforeDate:方法試圖獲取一個鎖亡哄,但是如果鎖沒有在規(guī)定的時間 內(nèi)被獲得,它會讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回 NO)布疙。

NSLock 頭文件中只有如下方法蚊惯,成員變量

- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name 
BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */

if ([theLock tryLock]) {
/* Update display used by all threads. */
   
[theLock unlock];
}
3.4 NSCondition條件鎖

NSCondition同樣實現(xiàn)了NSLocking協(xié)議,所以它和NSLock一樣灵临,也有NSLocking協(xié)議的lockunlock方法截型,可以當做NSLock來使用解決線程同步問題,用法完全一樣俱诸。同時菠劝,NSCondition提供更高級的用法赊舶。waitsignal睁搭,和條件信號量類似。

@interface TestViewController ()
/*
 創(chuàng)建一個數(shù)組盛放生產(chǎn)的數(shù)據(jù)笼平,創(chuàng)建一個線程鎖
*/
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *products;

 @end

 @implementation TestViewController
#pragma mark - event reponse
/*
 拖拽一個點擊事件园骆,創(chuàng)建兩個線程
*/
- (IBAction)coditionTest:(id)sender {
    NSLog(@"condiction 開始");
    [NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(createProducter) toTarget:self withObject:nil];
}
#pragma mark - provate methods
 - (void)createConsumenr{
     [self.condition lock];
     while(self.products.count == 0){
         NSLog(@"等待產(chǎn)品");
         [_condition wait];
     }
     [self.products removeObject:0];
     NSLog(@"消費產(chǎn)品");
     [_condition unlock];
 }

 - (void)createProducter{
     [self.condition lock];
     [self.products addObject:[[NSObject alloc] init]];
     NSLog(@"生產(chǎn)了一個產(chǎn)品");
     [_condition signal];
     [_condition unlock];
 }

 #pragma mark - getters and setters
 - (NSMutableArray *)products {
     if(_products == nil){
         _products = [[NSMutableArray alloc] initWithCapacity:0];
     }
     return _products;
 }

 - (NSCondition *)condition {
     if(_condition == nil){
         _condition = [[NSCondition alloc] init];
     }
     return _condition;
 }
 @end
3.5 NSRecursiveLock遞歸鎖

遞歸鎖可以被同一線程多次請求,而不會引起死鎖寓调,這主要是用在循環(huán)或遞歸操作中锌唾。

NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];

void MyRecursiveFunction(int value){
  [theLock lock];
  if (value != 0){
    --value;
    MyRecursiveFunction(value);
  }

  MyRecursiveFunction(5);
3.6 @synchronized

寫法最簡單 @synchronized( xx ), 也是性能消耗最高的鎖

- (void)getIamgeName:(int)index{
    NSString *imageName;
    @synchronized(self) {
        if (imageNames.count>0) {
            imageName = [imageNames lastObject];
            [imageNames removeObject:imageName];
        }
    }
}
3.7 OSSpinLock 被公認為不安全,不做描述

線程安全總結(jié)

  • 不可改變的對象一般是線程安全的。一旦你創(chuàng)建了它們夺英,你可以把這些對象在線程間安全的傳遞晌涕。另一方面,可變對象通常不是線程安全的痛悯。為了在多線程應(yīng)用 里面使用可變對象余黎,應(yīng)用必須適當?shù)耐健?/li>
  • 許多對象在多線程里面不安全的使用被視為是”線程不安全的”。只要同一時間 只有一個線程载萌,那么許多這些對象可以被多個線程使用惧财。這種被稱為專門限制應(yīng) 用程序的主線程的對象通常被這樣調(diào)用。
  • 應(yīng)用的主線程負責處理事件扭仁。盡管ApplicationKit在其他線程被包含在事件路 徑里面時還會繼續(xù)工作垮衷,但操作可能會被打亂順序。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乖坠,一起剝皮案震驚了整個濱河市搀突,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熊泵,老刑警劉巖描姚,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涩赢,死亡現(xiàn)場離奇詭異,居然都是意外死亡轩勘,警方通過查閱死者的電腦和手機筒扒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绊寻,“玉大人花墩,你說我怎么就攤上這事〕尾剑” “怎么了冰蘑?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長村缸。 經(jīng)常有香客問我祠肥,道長,這世上最難降的妖魔是什么梯皿? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任仇箱,我火速辦了婚禮,結(jié)果婚禮上东羹,老公的妹妹穿的比我還像新娘剂桥。我一直安慰自己,他們只是感情好属提,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布权逗。 她就那樣靜靜地躺著,像睡著了一般冤议。 火紅的嫁衣襯著肌膚如雪斟薇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天恕酸,我揣著相機與錄音堪滨,去河邊找鬼。 笑死尸疆,一個胖子當著我的面吹牛椿猎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寿弱,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼犯眠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了症革?” 一聲冷哼從身側(cè)響起筐咧,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后量蕊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铺罢,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年残炮,在試婚紗的時候發(fā)現(xiàn)自己被綠了韭赘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡势就,死狀恐怖泉瞻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苞冯,我是刑警寧澤袖牙,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站舅锄,受9級特大地震影響鞭达,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜皇忿,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一畴蹭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧禁添,春花似錦撮胧、人聲如沸桨踪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锻离。三九已至铺峭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汽纠,已是汗流浹背卫键。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虱朵,地道東北人莉炉。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像碴犬,于是被迫代替她去往敵國和親絮宁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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