iOS GCD之dispatch_semaphore(信號(hào)量)

前言

在看AFNetworking3.0源碼時(shí)藤乙,注意到在 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法中使用到dispatch_semaphore燕差,對(duì)dispatch_semaphore11不甚理解絮爷,經(jīng)查原來是通過引入信號(hào)量dispatch_semaphore``的方式把NSURLSession的異步方法 getTasksWithCompletionHandler: 變成了同步方法。

- (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);

    return tasks;
}

這里是把本來異步的getTasksWithCompletionHandler方法變成了同步的方式了欢峰,通過引入信號(hào)量的方式葬荷,等待異步方法獲取到tasks,然后再返回纽帖。


1. dispatch_semaphore介紹

  • 信號(hào)量是基于計(jì)數(shù)器的一種多線程同步機(jī)制宠漩,用來管理對(duì)資源的并發(fā)訪問
  • 信號(hào)量是基于計(jì)數(shù)器的一種多線程同步機(jī)制懊直,用來管理對(duì)資源的并發(fā)訪問扒吁。
  • 信號(hào)量就是一種可用來控制訪問資源的數(shù)量的標(biāo)識(shí),設(shè)定了一個(gè)信號(hào)量室囊,在線程訪問之前瘦陈,加上信號(hào)量的處理,則可告知系統(tǒng)按照我們指定的信號(hào)量數(shù)量來執(zhí)行多個(gè)線程波俄。
  • 其實(shí),這有點(diǎn)類似鎖機(jī)制了蛾默,只不過信號(hào)量都是系統(tǒng)幫助我們處理了懦铺,我們只需要在執(zhí)行線程之前,設(shè)定一個(gè)信號(hào)量值支鸡,并且在使用時(shí)冬念,加上信號(hào)量處理方法就行了趁窃。

簡(jiǎn)單來講 信號(hào)量為0則阻塞線程,大于0則不會(huì)阻塞急前。則我們通過改變信號(hào)量的值醒陆,來控制是否阻塞線程,從而達(dá)到線程同步裆针。

1.1 dispatch_semaphore相關(guān)的3個(gè)函數(shù)
  • dispatch_semaphore_create
// 創(chuàng)建信號(hào)量刨摩,參數(shù):信號(hào)量的初值,如果小于0則會(huì)返回NULL
dispatch_semaphore_t dispatch_semaphore_create(long value);
  • dispatch_semaphore_wait
// 等待降低信號(hào)量世吨,接收一個(gè)信號(hào)和時(shí)間值(多為DISPATCH_TIME_FOREVER)
// 若信號(hào)的信號(hào)量為0澡刹,則會(huì)阻塞當(dāng)前線程,直到信號(hào)量大于0或者經(jīng)過輸入的時(shí)間值耘婚;
// 若信號(hào)量大于0罢浇,則會(huì)使信號(hào)量減1并返回,程序繼續(xù)住下執(zhí)行
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
  • dispatch_semaphore_signal
// 提高信號(hào)量沐祷, 使信號(hào)量加1并返回
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore_waitdispatch_semaphore_signal這兩個(gè)函數(shù)中間的執(zhí)行代碼嚷闭,每次只會(huì)允許限定數(shù)量的線程進(jìn)入,這樣就有效的保證了在多線程環(huán)境下赖临,只能有限定數(shù)量的線程進(jìn)入胞锰。

可用于處理在多個(gè)線程訪問共有資源時(shí)候,會(huì)因?yàn)槎嗑€程的特性而引發(fā)數(shù)據(jù)出錯(cuò)的問題思杯。

1.2 線程同步方法
1.2.1 使用NSoperation下可以直接設(shè)置并發(fā)數(shù)
1.2.2 使用GCD讓線程同步方法
  • dispatch_group
  • dispatch_barrier
  • dispatch_semaphore

2.應(yīng)用場(chǎng)景

2.1 保持線程同步胜蛉,將異步操作轉(zhuǎn)換為同步操作
// 保持線程同步
- (void)semaphore {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int j = 0;
    dispatch_async(queue, ^{
        j = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"finish j = %zd", j);
}

輸出結(jié)果

image.png

如果注掉dispatch_semaphore_wait這一行,則 j = 0

  • 注釋:block塊異步執(zhí)行添加到了全局并發(fā)隊(duì)列里色乾,所以程序在主線程會(huì)跳過block塊(同時(shí)開辟子線程異步執(zhí)行block塊)誊册,執(zhí)行塊外的代碼dispatch_semaphore_wait,因?yàn)閟emaphore信號(hào)量為0暖璧,且時(shí)間為DISPATCH_TIME_FOREVER案怯,所以會(huì)阻塞當(dāng)前線程(主線程),進(jìn)而只執(zhí)行子線程的block塊澎办,直到執(zhí)行塊內(nèi)部的dispatch_semaphore_signal使得信號(hào)量+1嘲碱。正在被阻塞的線程(主線程)會(huì)恢復(fù)繼續(xù)執(zhí)行。這樣保證了線程之間的同步局蚀。
2.2 為線程加鎖
// 給線程加鎖
- (void)addLock {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            // 相當(dāng)于加鎖
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"i = %zd semaphore = %@", i, semaphore);
            // 相當(dāng)于解鎖
            dispatch_semaphore_signal(semaphore);
        });
    }
}

輸出結(jié)果:

image.png
  • 注釋:當(dāng)線程1執(zhí)行到dispatch_semaphore_wait這一行時(shí)麦锯,semaphore的信號(hào)量為1,所以使信號(hào)量-1變?yōu)?琅绅,并且線程1繼續(xù)往下執(zhí)行扶欣;如果當(dāng)在線程1 NSLog這一行代碼還沒執(zhí)行完的時(shí)候,又有線程2來訪問,執(zhí)行dispatch_semaphore_wait時(shí)由于此時(shí)信號(hào)量為0料祠,且時(shí)間為DISPATCH_TIME_FOREVER,所以會(huì)一直阻塞線程2(此時(shí)線程2處于等待狀態(tài))骆捧,直到線程1執(zhí)行完NSLog并執(zhí)行完dispatch_semaphore_signal使信號(hào)量為1后,線程2才能解除阻塞繼續(xù)住下執(zhí)行髓绽。以上可以保證同時(shí)只有一個(gè)線程執(zhí)行NSLog這一行代碼敛苇。
2.3 獲取通訊錄
  • 做通訊錄的時(shí)候需要判斷權(quán)限,才能獲取通訊錄
// 獲取通訊錄
- (void)getAddressBook {
    //這個(gè)變量用于記錄授權(quán)是否成功顺呕,即用戶是否允許我們?cè)L問通訊錄
    __block int tip=0;
    
    //創(chuàng)建通訊簿的引用
    ABAddressBookRef addressBooks=ABAddressBookCreateWithOptions(NULL, NULL);
    //創(chuàng)建一個(gè)初始信號(hào)量為0的信號(hào)
    dispatch_semaphore_t sema=dispatch_semaphore_create(0);
    //申請(qǐng)?jiān)L問權(quán)限
    ABAddressBookRequestAccessWithCompletion(addressBooks, ^(bool granted, CFErrorRef error)        {
        //granted為YES是表示用戶允許枫攀,否則為不允許
        if (!granted) {
            tip=1;
        }
        //發(fā)送一次信號(hào)
        dispatch_semaphore_signal(sema);
    });
    //等待信號(hào)觸發(fā)
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    CFRelease(addressBooks);
}
2.4 使用 Dispatch Semaphore 控制并發(fā)線程數(shù)量
// 控制并發(fā)線程數(shù)量
- (void)dispatchAsyncLimit:(dispatch_queue_t)queue limitSemaphoreCount:(NSUInteger)limitSemaphoreCount bloc:(dispatch_block_t)block {
    //控制并發(fā)數(shù)的信號(hào)量
    static dispatch_semaphore_t limitSemaphore;
    
    //專門控制并發(fā)等待的線程
    static dispatch_queue_t receiverQueue;
    
    //使用 dispatch_once而非 lazy 模式,防止可能的多線程搶占問題
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        limitSemaphore = dispatch_semaphore_create(limitSemaphoreCount);
        receiverQueue = dispatch_queue_create("receiver", DISPATCH_QUEUE_SERIAL);
    });
    
    // 如不加 receiverQueue 放在主線程會(huì)阻塞主線程
    dispatch_async(receiverQueue, ^{
        //可用信號(hào)量后才能繼續(xù)塘匣,否則等待
        dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(queue, ^{
            !block ? : block();
            //在該工作線程執(zhí)行完成后釋放信號(hào)量
            dispatch_semaphore_signal(limitSemaphore);
        });
    });
}
  • 注釋:以上栗子有點(diǎn)像-[NSOperationQueue maxConcurrentOperationCount]脓豪。 在能保證靈活性的情況下,通常更好的做法是使用操作隊(duì)列忌卤,而不是通過GCD和信號(hào)量來構(gòu)建自己的解決方案扫夜。

信號(hào)量屬于底層工具。它非常強(qiáng)大驰徊,但在多數(shù)需要使用它的場(chǎng)合笤闯,最好從設(shè)計(jì)角度重新考慮,看是否可以不用棍厂。應(yīng)該優(yōu)先考慮是否可以使用諸如操作隊(duì)列這樣的高級(jí)工具颗味。通常可以通過增加一個(gè)分派隊(duì)列dispatch_suspend牺弹,或者通過其他方式分解操作來避免使用信號(hào)量浦马。信號(hào)量并非不好,只是它本身是鎖张漂,能不用鎖就不要用晶默。盡量用cocoa框架中的高級(jí)抽象,信號(hào)量非常接近底層航攒。但有時(shí)候磺陡,例如需要把異步任務(wù)轉(zhuǎn)換為同步任務(wù)時(shí),信號(hào)量是最合適的工具漠畜。

2.5 同時(shí)下載多張圖片

問題描述:
1.假設(shè)現(xiàn)在系統(tǒng)有兩個(gè)空閑資源可以被利用币他,但同一時(shí)間卻有三個(gè)線程要進(jìn)行訪問,這種情況下憔狞,該如何處理呢蝴悉?

2.我們要下載很多圖片,并發(fā)異步進(jìn)行瘾敢,每個(gè)下載都會(huì)開辟一個(gè)新線程辫封,可是我們又擔(dān)心太多線程肯定cpu吃不消硝枉,那么我們這里也可以用信號(hào)量控制一下最大開辟線程數(shù)。

代碼如下

- (void)dispatchSignal {
    // crate的value表示倦微,最多幾個(gè)資源可訪問
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    // 隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //任務(wù)1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任務(wù)2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    //任務(wù)3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
}

輸出結(jié)果:

dispatchSignal.png
  • 總結(jié):由于設(shè)定的信號(hào)值為2,先執(zhí)行兩個(gè)線程正压,等執(zhí)行完一個(gè)欣福,才會(huì)繼續(xù)執(zhí)行下一個(gè),保證同一時(shí)間執(zhí)行的線程數(shù)不超過2焦履。

假設(shè)我們?cè)O(shè)定信號(hào)值=1

// crate的value表示拓劝,最多幾個(gè)資源可訪問
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

輸出結(jié)果:

image.png

假設(shè)我們?cè)O(shè)定信號(hào)值=3,就是不限制線程執(zhí)行了,因?yàn)橐还膊胖挥?個(gè)線程嘉裤。

// crate的value表示郑临,最多幾個(gè)資源可訪問
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);

輸出結(jié)果:

image.png

以上只是舉的比較簡(jiǎn)單的例子,在一些特殊場(chǎng)景下屑宠,合理利用信號(hào)量去控制厢洞,能夠方便的解決我們的難題。


本文參考
iOS GCD之dispatch_semaphore(信號(hào)量)
iOS GCD中級(jí)篇 - dispatch_semaphore(信號(hào)量)的理解及使用
非常感謝以上作者


iOS多線程詳細(xì)總結(jié)系列文章
iOS GCD之dispatch_semaphore(信號(hào)量)
iOS 多線程-GCD 詳細(xì)總結(jié)
iOS 多線程: [NSOperation NSOperationQueue] 詳解
iOS 多線程:[pthread,NSThread]詳細(xì)總結(jié)


項(xiàng)目源碼鏈接地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市握截,隨后出現(xiàn)的幾起案子谣旁,更是在濱河造成了極大的恐慌,老刑警劉巖祟绊,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡陕靠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門脱茉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剪芥,“玉大人,你說我怎么就攤上這事芦劣〈志悖” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵虚吟,是天一觀的道長(zhǎng)寸认。 經(jīng)常有香客問我,道長(zhǎng)串慰,這世上最難降的妖魔是什么偏塞? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮邦鲫,結(jié)果婚禮上灸叼,老公的妹妹穿的比我還像新娘神汹。我一直安慰自己,他們只是感情好古今,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布屁魏。 她就那樣靜靜地躺著,像睡著了一般捉腥。 火紅的嫁衣襯著肌膚如雪氓拼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天抵碟,我揣著相機(jī)與錄音桃漾,去河邊找鬼。 笑死拟逮,一個(gè)胖子當(dāng)著我的面吹牛撬统,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敦迄,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼恋追,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了颅崩?” 一聲冷哼從身側(cè)響起几于,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沿后,沒想到半個(gè)月后沿彭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尖滚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年喉刘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漆弄。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睦裳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撼唾,到底是詐尸還是另有隱情廉邑,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布倒谷,位于F島的核電站蛛蒙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏渤愁。R本人自食惡果不足惜牵祟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抖格。 院中可真熱鬧诺苹,春花似錦咕晋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坪哄,卻和暖如春站辉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背损姜。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殊霞,地道東北人摧阅。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绷蹲,于是被迫代替她去往敵國(guó)和親棒卷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法祝钢。通過本文比规,您將了解到: 1. GCD 簡(jiǎn)介 2. G...
    曉_我想去環(huán)游世界閱讀 1,142評(píng)論 2 8
  • 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)拦英、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨(dú)行者閱讀 500評(píng)論 0 1
  • 很久前的總結(jié)蜒什,今天貼出來。適合看了就用疤估,很少講解灾常,純粹用法。 目錄 Dispatch Queue dispatch...
    和女神經(jīng)常玩閱讀 646評(píng)論 0 3
  • 前陣子在找思維導(dǎo)圖app的時(shí)候铃拇,應(yīng)用商店給我推薦了簡(jiǎn)書钞瀑。簡(jiǎn)書這個(gè)名字讓我眼前一亮,有種干凈簡(jiǎn)單的感覺慷荔,讓...
    雙木朧月閱讀 189評(píng)論 1 1
  • 日精進(jìn)第八日 日閱讀:《中國(guó)社會(huì)各階層分析》 日鍛煉:早起跑步1500米雕什,步行若干 日總結(jié)反思:閱讀時(shí)看到一句以色...
    林予懷_閱讀 276評(píng)論 0 0