iOS GCD全析(四)

本文摘錄自《Objective-C高級編程》一書菊霜,附加一些自己的理解,作為對GCD的總結(jié)济赎。



此篇主要包含以下幾個方面:

  • dispatch_suspend / dispatch_resume

  • dispatch_once

  • Dispatch Semaphore

    • dispatch_semaphore_t
    • dispatch_semaphore_create()
    • dispatch_semaphore_wait()
    • dispatch_semaphore_signal()


dispatch_suspend / dispatch_resume

當追加大量處理到Dispatch Queue時鉴逞,在追加處理的過程中,有時希望不執(zhí)行已追加的處理联喘。例如演算結(jié)果被Block截獲時华蜒,一些處理會對這個演算結(jié)果造成影響辙纬。

在這種情況下豁遭,只要掛起Dispatch Queue即可。當可以執(zhí)行時再恢復贺拣。

dispatch_suspend 函數(shù)掛起指定的Dispatch Queue蓖谢。

dispatch_suspend(queue);

dispatch_resume 函數(shù)恢復指定的Dispatch Queue譬涡。

dispatch_resume(queue)闪幽;

這些函數(shù)對已經(jīng)執(zhí)行的處理沒有影響。掛起后涡匀,追加到Dispatch Queue中但尚未執(zhí)行的處理在此之后停止執(zhí)行盯腌。而恢復則使得這些處理能夠繼續(xù)執(zhí)行。

注:dispatch_suspend 函數(shù)和 dispatch_resume 函數(shù)都可以用在Dispatch Source陨瘩,而掛起和恢復的就是dispatch_source_set_event_handler 函數(shù)的回調(diào)腕够。



dispatch_once

dispatch_once函數(shù)是保證在應用程序執(zhí)行中只執(zhí)行一次指定處理的API级乍。下面這種經(jīng)常出現(xiàn)的用來進行初始化的源代碼可通過dispatch_once函數(shù)簡化。

static int initialized = NO;

if (initialized == NO) {
    /*
     * 初始化
     */
    initialized = YES;
}

如果使用dispatch_once函數(shù)帚湘,則源代碼寫為:

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
    /*
     * 初始化
     */
});

源代碼看起來沒有太大的變化玫荣。但是通過dispatch_once函數(shù),該源代碼即使在多線程環(huán)境下執(zhí)行大诸,也可保證百分之百安全捅厂。

之前的源代碼在大多數(shù)情況下也是安全的。但是在多核CPU中资柔,在正在更新表示是否初始化的標志變量時讀取焙贷,就有可能多次執(zhí)行初始化處理。而用dispatch_once函數(shù)初始化就不必擔心這樣的問題贿堰。這就是所說的單例模式盈厘,在生成單例對象時使用。



Dispatch Semaphore

當并行執(zhí)行的處理更新數(shù)據(jù)時即多個線程同時訪問同一數(shù)據(jù)官边,會產(chǎn)生數(shù)據(jù)不一致的情況沸手,有時應用程序還會異常結(jié)束。雖然使用Serial Dispatch Queue 和dispatch_barier_async函數(shù)可避免這類問題注簿,但有必要進行更細粒度的排他控制契吉。

我們來思考一下這種情況:使用兩個線程去訪問同一個數(shù)據(jù),以下代碼countNumber最終結(jié)果是多少诡渴。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 10000; i++) {
        self.countNumber = self.countNumber + 1;
        NSLog(@"%d",self.countNumber);
    }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 10000; i++) {
        self.countNumber = self.countNumber + 1;
        NSLog(@"%d",self.countNumber);
    }
});

最終結(jié)果并不是20000捐晶,這就是典型的線程安全問題。

因為該源代碼使用Global Dispatch Queue 更新countNumber屬性妄辩,所以執(zhí)行后數(shù)據(jù)有很高概率并不是實時有效的惑灵,程序很可能異常結(jié)束。此時應使用Dispatch Semaphore眼耀。

Dispatch Semaphore本來使用的是更細粒度的對象英支,不過本書還是使用該源代碼對Dispatch Semaphore進行說明。

Dispatch Semaphore是持有計數(shù)的信號哮伟,該計數(shù)是多線程編程中的計數(shù)類型信號干花。所謂信號,類似于過馬路時常用的手旗楞黄〕仄啵可以通過時舉起手旗,不可通過時放下手旗鬼廓。而在Dispatch Semaphore中肿仑,使用計數(shù)來實現(xiàn)該功能。計數(shù)為0時等待,計數(shù)為1或大于1時代碼可通過尤慰。

下面介紹一下使用方法勾邦。通過dispatch_semaphore_create函數(shù)生成Dispatch Semaphore。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

參數(shù)表示計數(shù)的初始值割择。本例將計數(shù)值初始化為“1”眷篇。

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函數(shù)等待Dispatch Semaphore的計數(shù)值達到大于或等于1。當計數(shù)值大于等于1荔泳,或者在待機中計數(shù)值大于等于1時蕉饼,對該計數(shù)進行減法并從dispatch_semaphore_wait函數(shù)返回。第二個參數(shù)與dispatch_group_wait函數(shù)等相同玛歌,由dispatch_time_t類型值指定等待時間昧港。該例的參數(shù)意味著永久等待。另外支子,dispatch_ semaphore_wait函數(shù)的返回值也與dispatch_group_wait函數(shù)相同创肥。可像以下源代碼這樣值朋,通過返回值進行分支處理叹侄。

dispatch_semaphore_t sem = dispatch_semaphore_create(1);

long result = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

if (result == 0) {
    
    /*
     * 由于 Dispatch Semaphore 的計數(shù)值達到大于等于1
     * 或者在待機中的指定時間內(nèi) Dispatch Semaphore 的計數(shù)值達到大于等于1
     * 所以 Dispatch Semaphore 的計數(shù)值減去1。
     *
     * 可執(zhí)行需要進行排他控制的處理
     */
}
else {
    /*
     * 由于 Dispatch Semaphore 的計數(shù)值為0
     * 因此在達到指定時間為止待機
     */
}

dispatch_semaphore_wait函數(shù)返回0時昨登,可安全地執(zhí)行需要進行排他控制的處理趾代。該處理結(jié)束時通過dispatch _semaphore_signal函數(shù)將Dispatch Semaphore的計數(shù)值加1。

我們在前面的源代碼中實際使用Dispatch Semaphore看看丰辣。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/*
 * 生成 Dispatch Semaphore撒强。
 *
 * Dispatch Semaphore 的計數(shù)初始值設定為“1”。
 *
 * 保證可訪問 countNumber 屬性的線程
 * 同時只能有一個
 */

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_async(queue, ^{
    for (int i = 0; i < 10000; i++) {
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        /*
         * 執(zhí)行過 dispatch_semaphore_wait 后計數(shù)為 0 笙什,其它線程需要等待 Dispatch Semaphore 飘哨,
         *
         * 一直等待,直到 Dispatch Semaphore 的計數(shù)值達到大于等于 1 琐凭。
         *
         * 由于可訪問 countNumber 屬性的線程只有一個
         * 因此可以安全的進行更新
         */
        
        self.countNumber += 1;
        
        dispatch_semaphore_signal(semaphore);
        /*
         * 排他控制處理結(jié)束芽隆,
         * 所以通過 dispatch_semaphore_signal 函數(shù)
         * 將 Dispatch Semaphore 的計數(shù)值加 1。
         * 如果有通過 dispatch_semaphore_wait 函數(shù)
         * 等待 Dispatch Semaphore 的計數(shù)值增加的線程淘正,
         * 就由最先等待的線程執(zhí)行摆马。
         */
        
        NSLog(@"%d",self.countNumber);
    }
});

dispatch_async(queue, ^{
    for (int i = 0; i < 10000; i++) {
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        self.countNumber = self.countNumber + 1;
        
        dispatch_semaphore_signal(semaphore);
        
        NSLog(@"%d",self.countNumber);
    }
});

這樣就保證了線程安全,最后的結(jié)果是20000鸿吆。

在沒有Serial Dispatch Queue和 dispatch_barrier_async 函數(shù)那么大粒度且一部分處理需要進行排他控制的情況下,Dispatch Semaphore 便可發(fā)揮威力述呐。

《關于dispatch_semaphore的使用》中有這樣的描述:

??停車場剩余4個車位惩淳,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待思犁。
?
??信號量的值就相當于剩余車位的數(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 )就只能等待策泣。有可能同時有幾輛車等待一個停車位衙傀。有些車主沒有耐心,給自己設定了一段等待時間萨咕,這段時間內(nèi)等不到停車位就走了统抬,如果等到了就開進去停車。而有些車主就像把車停在這危队,所以就一直等下去聪建。

Parse源碼淺析系列(一)---Parse的底層多線程處理思路:GCD高級用法中這樣描述:

??在這個停車場系統(tǒng)中,車位是公共資源茫陆,每輛車好比一個線程妆偏,看門人起的就是信號量的作用。 更進一步盅弛,信號量的特性如下:信號量是一個非負整數(shù)(車位數(shù))钱骂,所有通過它的線程(車輛)都會將該整數(shù)減一(通過它當然是為了使用資源),當該整數(shù)值為零時挪鹏,所有試圖通過它的線程都將處于等待狀態(tài)见秽。在信號量上我們定義兩種操作: Wait(等待) 和 Release(釋放)。 當一個線程調(diào)用Wait(等待)操作時讨盒,它要么通過然后將信號量減一解取,要么一直等下去,直到信號量大于一或超時返顺。Release(釋放)實際上是在信號量上執(zhí)行加操作禀苦,對應于車輛離開停車場,該操作之所以叫做“釋放”是因為加操作實際上是釋放了由信號量守護的資源遂鹊。
?
??從 iOS7 升到 iOS8 后振乏,GCD 出現(xiàn)了一個重大的變化:在 iOS7 時,使用 GCD 的并行隊列秉扑, dispatch_async 最大開啟的線程一直能控制在6慧邮、7條,線程數(shù)都是個位數(shù),然而 iOS8后误澳,最大線程數(shù)一度可以達到40條耻矮、50條。然而在文檔上并沒有對這一做法的目的進行介紹忆谓。
?
??筆者推測 Apple 的目的是想借此讓開發(fā)者使用 NSOperationQueue :GCD 中 Apple 并沒有提供控制并發(fā)數(shù)量的接口裆装,而 NSOperationQueue 有,如果需要使用 GCD 實現(xiàn)倡缠,需要使用 GCD 的一項高級功能:Dispatch Semaphore信號量哨免。

接下來我們使用Dispatch Semaphore來控制GCD的并發(fā)數(shù)

設置并發(fā)數(shù)為 3,即最多有三條線程同時執(zhí)行

- (void)viewDidLoad {
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
    dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
    
    unsigned int sleepTime = 2;
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 1000; i++) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"%@",[NSThread currentThread]);
            sleep(sleepTime);
            dispatch_semaphore_signal(semaphore);
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 1000; i++) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"%@",[NSThread currentThread]);
            sleep(sleepTime);
            dispatch_semaphore_signal(semaphore);
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 1000; i++) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"%@",[NSThread currentThread]);
            sleep(sleepTime);
            dispatch_semaphore_signal(semaphore);
        }
    });
}

我們在代碼中讓每條線程每兩秒執(zhí)行一次毡琉,放慢速度后就容易看出同時有幾條線程在執(zhí)行




我們把dispatch_semaphore_create()參數(shù)換成 2铁瞒,即dispatch_semaphore_create(2),其它代碼原封不動桅滋,下面是運行結(jié)果



由上述結(jié)果可以看出慧耍,我們給并發(fā)隊列異步添加了3個任務,如果沒有限制的情況下會創(chuàng)建3條子線程同時執(zhí)行丐谋。當我們把dispatch_semaphore_create()參數(shù)設為 3 的時候芍碧,3 條線程的確同時執(zhí)行,當我們換成 2 的時候号俐,就只剩下兩條線程在同時執(zhí)行泌豆。這就驗證了dispatch_semaphore_create()的參數(shù)是可以控制并發(fā)數(shù)的說法。

所以吏饿,我們很多時候看到在網(wǎng)上各類博文中出現(xiàn)的dispatch_semaphore_create(1)踪危,這種情況大多被當做線程鎖來使用是沒有問題的。因為參數(shù)為1猪落,所以同時執(zhí)行的線程只能有1個贞远,達到了線程鎖要求的效果。按照dispatch_semaphore_create()的原理笨忌,與自旋鎖不同蓝仲,卻類似于互斥鎖,具有線程的排他性官疲。

?著作權(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)容