信號(hào)量的用法(解決異步線程中的線程等待問題)

http://www.itdecent.cn/p/888ea823c8a5
http://www.itdecent.cn/p/1e9f3a5f4932

hello,各位讀者,我又回來了啦,感覺上一篇的文章各位的反映還算不錯(cuò),感謝各位讓我有堅(jiān)持寫作的動(dòng)力。好了,前話就說這么多了,開始我們今天要說的主題了,最近博主在開發(fā)中碰到一個(gè)問題,開啟兩個(gè)主要異步線程,兩個(gè)異步線程內(nèi)部又得分別開啟一個(gè)異步線程和其他耗時(shí)操作,最后還有第三個(gè)線程,這第三個(gè)線程必須等到前兩個(gè)主要線程內(nèi)部所有操作都完成以后再去執(zhí)行,但是在執(zhí)行以上這些操作的時(shí)候不能卡住界面,以下是我簡單畫的一個(gè)需求分析圖

image

可能有些讀者一眼看下去,會(huì)覺得這不是一個(gè)簡單的線程組能搞掂的事情嘛?也許是的,那么我們來嘗試一下,對(duì)需求進(jìn)行線程組的功能制作

1.第一個(gè)主要線程

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
    NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      // 請(qǐng)求完成,可以通知界面刷新界面等操作
      NSLog(@"第一步網(wǎng)絡(luò)請(qǐng)求完成");
    }];
    [task resume];
      // 以下還要進(jìn)行一些其他的耗時(shí)操作
      NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
});

2.第二個(gè)主要線程(跟第一線程的操作是一樣的。但是請(qǐng)求的地址不一樣)

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
    NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      // 請(qǐng)求完成,可以通知界面刷新界面等操作
      NSLog(@"第二步網(wǎng)絡(luò)請(qǐng)求完成");
    }];
    [task resume];
      // 以下還要進(jìn)行一些其他的耗時(shí)操作
      NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
});

3.第三個(gè)主要線程(等待前兩個(gè)線程完全完成后再進(jìn)行)

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"刷新界面等在主線程的操作");
});


那么,上面代碼一眼看下去就很明了了,開啟一個(gè)線程組,然后兩個(gè)異步線程組 dispatch_group_t,最后一個(gè) dispatch_group_notify 來執(zhí)行依賴操作,但是當(dāng)博主開開心心的跑去 command + R 的時(shí)候,奇怪的事情發(fā)生了,以下是是輸出的結(jié)果:

1 `NSLog(@"第一步網(wǎng)絡(luò)請(qǐng)求完成");`
2 `NSLog(@"刷新界面等在主線程的操作");`
3 `NSLog(@"第二步網(wǎng)絡(luò)請(qǐng)求完成");`

讀者不難發(fā)現(xiàn),我們的數(shù)序不對(duì)了,第三步操作(刷新界面操作)明明是要第一步和第二步操作完成后才能進(jìn)行的,我們已經(jīng)設(shè)置了 dispatch_group_notify 了,但是為什么不是按照我們的思路去走呢?
其實(shí)這個(gè)道理很簡單,我們開啟的網(wǎng)絡(luò)請(qǐng)求,是一個(gè)異步線程,所謂的異步線程,就是告訴系統(tǒng)你不要管我是否完成了,你盡管執(zhí)行其他操作,開一個(gè)線程讓我到外面操作去執(zhí)行就行了,所以我們傻傻的 dispatch_group_async 自然就不會(huì)管網(wǎng)絡(luò)操作是否完成,是否有數(shù)據(jù)了,直接執(zhí)行下面操作,告訴 dispatch_group_notify 它已經(jīng)完成就行了。


但這怎么辦好呢?所以我們要引入了我們今天要用到的 多線程的信號(hào)量 dispatch_semaphore_t 了,那么 dispatch_semaphore_t又怎么理解么?

` dispatch_semaphore_t` :通俗的說我們可以理解成他是一個(gè)紅綠燈的信號(hào),當(dāng)它的信號(hào)量為0時(shí)(紅燈)等待,
  當(dāng)信號(hào)量為1或大于1時(shí)(綠燈)走。

以下就是它的創(chuàng)建跟使用:

// 創(chuàng)建一個(gè)信號(hào),value:信號(hào)量
dispatch_semaphore_create(<#long value#>)
// 使某個(gè)信號(hào)的信號(hào)量+1
dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)
// 某個(gè)信號(hào)進(jìn)行等待, timeout:等待時(shí)間,永遠(yuǎn)等待為 DISPATCH_TIME_FOREVER
dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)

那么我們的代碼可以改寫為

// 設(shè)置一個(gè)異步線程組
dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
    // 設(shè)置一個(gè)網(wǎng)絡(luò)請(qǐng)求
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
    // 創(chuàng)建一個(gè)信號(hào)量為0的信號(hào)(紅燈)
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"第一步操作");
        // 使信號(hào)的信號(hào)量+1,這里的信號(hào)量本來為0,+1信號(hào)量為1(綠燈)
        dispatch_semaphore_signal(sema);
    }];
    [task resume];
     // 以下還要進(jìn)行一些其他的耗時(shí)操作
      NSLog(@"耗時(shí)操作繼續(xù)進(jìn)行");
    // 開啟信號(hào)等待,設(shè)置等待時(shí)間為永久,直到信號(hào)的信號(hào)量大于等于1(綠燈)
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});

根據(jù)上面的寫法,當(dāng)線程執(zhí)行到 dispatch_semaphore_wait 的時(shí)候如果網(wǎng)絡(luò)請(qǐng)求還沒有完成,那么信號(hào)就會(huì)繼續(xù)等待,這個(gè)異步線程組就不會(huì)執(zhí)行完畢,這樣就能達(dá)到我們的需求了。

  當(dāng)然  `dispatch_semaphore_signal` 的用途可不止這么一個(gè)喔,有興趣的讀者可以去多看看喔。

@end


關(guān)于信號(hào)量,一般可以用停車來比喻。
停車場剩余4個(gè)車位,那么即使同時(shí)來了四輛車也能停的下。如果此時(shí)來了五輛車,那么就有一輛需要等待。
信號(hào)量的值就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait函數(shù)就相當(dāng)于來了一輛車,dispatch_semaphore_signal
就相當(dāng)于走了一輛車。停車位的剩余數(shù)目在初始化的時(shí)候就已經(jīng)指明了(dispatch_semaphore_create(long value)),
調(diào)用一次dispatch_semaphore_signal,剩余的車位就增加一個(gè);調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個(gè);
當(dāng)剩余車位為0時(shí),再來車(即調(diào)用dispatch_semaphore_wait)就只能等待。有可能同時(shí)有幾輛車等待一個(gè)停車位。有些車主
沒有耐心,給自己設(shè)定了一段等待時(shí)間,這段時(shí)間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就像把車停在這,
所以就一直等下去。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容