iOS 多線程 之 GCD

左手畫方 右手畫圓

「少俠,你能左手畫方,又手畫圓嗎?」bash

「這有何難,開始看我表演吧」多線程

左方右圓

「看,怎麼樣,厲害吧」異步

「鵝 .... 厲害...」async

「少俠,其實我是說你在iOS代碼裏能左手畫方,右手畫圓嗎?」ui

「原來是這個意思,就是多線程嘛,這有何難,看我用GCD展現一下」spa

NSLog(@"左手畫方");  

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
      NSLog(@"右手畫圓");
});

//輸出
左手畫方
右手畫圓
複製代碼

「怎麼樣厲害吧」線程

「少俠確實有兩下子,那對於GCD,少俠還有其餘用法嘛」code

「啥?其餘用法,不須要的,只須要如上我這等操做,就能解決大部分問題,其餘用法,不須要的。」cdn

「非也、非也,GCD這本祕籍仍是有不少招式的,且聽老夫給你細細道來。」blog

GCD的四個基本心法

同步 畫-完-告

小師妹讓你畫一個方,收到任務你開始畫方,畫完以後告知小師妹,方已完成。同步要阻塞當前線程,必需要等待當前任務執行完,返回之後,纔會繼續執行下一個任務。

異步 畫-立-告

小師妹讓你畫一個方,收到任務後,爲了顯示你的厲害所在,你立馬告知小師妹,方我已經着人去畫啦,你且等着吧,很快就有消息了。異步不會阻塞當前線程,會當即返回,可馬上執行下一個任務。

串行隊列 一心一用

小師妹讓你畫一方一圓,收到任務,你的所有注意力放在一個地方,左手畫方的時候,所有注意力在左手,因此此時右手是不動的,等待左手畫完方以後,所有注意力集中右手,這時右手再畫圓。此爲先左手畫方,後右手畫圓。一心一用理解爲串行,串行隊列中的任務只能一次執行一個。

串行隊列

並行隊列 一心兩用

你的注意力分開兩部分,一部分在左手,一部分在右手,而後左手畫方,以後立馬開始右手畫圓,一心兩用理解爲並行。並行隊列中的任務會按照加入隊列中的順序執行,但哪一個任務開始執行完是不定的。

並行隊列

GCD的七個基本招式

「等等,大師,你的這些心法,貌似沒什麼用啊,個人招式仍是能解決大部門問題啊」

「那老夫就來問幾個小問題,看招」

「多個線程同時讀寫一份數據,如何作到不出錯?
多個異步任務都完成以後,才能告知小師妹,如何作到? 」

「鵝....」

「嘿嘿,不懂了吧,下面老夫就來給你展現GCD的八個基本招式,學會基本招式,這些問題就迎刃而解了」

dispatch_sync

NSLog(@"畫方");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       NSLog(@"畫圓");
});
NSLog(@"吹口哨")

//輸出
畫方
畫圓
吹口哨
複製代碼

dispatch_sync 會阻塞當前線程,等待block中的任務執行完畢後,纔會執行下一個任務,因此執行順序是 畫方-畫圓-吹口哨。

dispatch_async

NSLog(@"畫方");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       NSLog(@"畫圓");
});
NSLog(@"吹口哨")

//輸出
畫方
吹口哨
畫圓
複製代碼

dispatch_async 不會阻塞當前線程,會當即執行下一個任務。因此執行順序是 畫方-吹口哨-畫圓。

dispatch_after

指定時間後,將要執行的任務添加到指定的隊列中去。

//兩秒以後吹口哨
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSLog(@"吹口哨");
});
複製代碼

示例中2秒鐘以後,將任務添加到主隊列中,若是主隊列中這時有其餘的耗時操做,那麼將會等待主隊列中的任務執行完之後,纔會執行添加的任務。 因此dispatch_after是不許確的。

dispatch_once

+ (instancetype)shareUserInfo
{
    static UserInfo *shareUserInfo = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareUserInfo = [[UserInfo alloc] init];
    });
    return shareUserInfo;
}
複製代碼

dispatch_once能夠幫助咱們建立單例,不再用像之前同樣費心費力啦。

dispatch_barrier

在使用dispatch_barrier的隊列中,執行順序以下:

  • 先於dispatch_barrier以前加入隊列的任務先執行。
  • dispatch_barrier 中的任務執行。
  • 晚於dispatch_barrier添加到隊列的任務最後執行。
//不使用dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"畫方");
    });
    
dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"畫圓");
});
    
NSLog(@"吹口哨");
    
dispatch_async(queue, ^{
    NSLog(@"撩師妹");
});
dispatch_async(queue, ^{
    NSLog(@"告師傅");
}); 

//輸出
吹口哨
撩師妹
告師傅
畫方
畫圓  
複製代碼

由於畫方畫圓的時間過久,致使了活兒還沒幹,就先吹口哨了,這是不對的。使用dispatch_barrier再試一次。

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    sleep(2);
    NSLog(@"畫方");
});
    
dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"畫圓");
});

dispatch_barrier_sync(queue, ^{
    sleep(4);
    NSLog(@"吹口哨");
});
    
dispatch_async(queue, ^{
    NSLog(@"撩師妹");
});
dispatch_async(queue, ^{
    NSLog(@"告師傅");
}); 

//輸出
畫方
畫圓
吹口哨
撩師妹
告師傅
複製代碼

能夠看到雖然畫方畫圓的時間比較久,吹口哨由於技術不成熟,也耗時較久,但最終是按照正常流程進行,活兒沒幹完堅定不撩師妹。

撩師妹

清楚了吧,吹口哨的時候,其餘的都靠邊站,就是這麼橫。

「大師,爲何圖中是並行隊列,而不是串行隊列?」

「由於串行隊列原本就是按順序執行的...」

dispatch_group

畫方-畫圓-吹口哨-撩師妹-告師傅,流程行雲流水。 如今新的問題來了,那想在畫方-畫圓以後,喝口水再吹口哨要怎麼作呢?

首先來分析一下問題:

  1. 畫方畫圓屬於耗時的任務,如今把它們放在了異步線程。異步線程的執行時間,是不知道的。
  2. 兩個異步線程任務都執行完以後,喝水。

要達到上面的要求,一個比較普通的流程是畫方完成後用一個變量記錄,畫圓完成後用另一個變量記錄。當兩個變量都記錄到完成以後,就能夠繼續下一步了,喝水。

麻煩嗎? 也不麻煩,才兩個變量而已。可是上面的耗時操做若是是5個呢? 麻煩! dispatch_group 能夠方便的解決這個問題。

dispatch_group_t group = dispatch_group_create();
    
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    sleep(3);
    NSLog(@"畫方完成");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    sleep(2);
    NSLog(@"畫圓完成");
    dispatch_group_leave(group);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"都完成了 喝口水");
});

//輸出 
畫圓完成
畫方完成
都完成了 喝口水
複製代碼

少俠你看,前面提到的問題之一:「多個異步任務都完成以後,才能告知小師妹,如何作到?」 就這樣解決了。

有一點要注意,dispatch__group__enterdispatch__group__leave必須成對出現,否則會崩潰的。

dispatch_semaphore

信號量能夠控制多個線程對有限資源的訪問。

dispatch_semaphore只有三個方法:

//建立信號量
dispatch_semaphore_create
//發送信號量
dispatch_semaphore_signal
//等待信號量
dispatch_semaphore_wait
複製代碼

以少俠爲例:

少俠如今要去馬場,給你的汗血寶馬喂草,可是馬場的馬槽是有限的,其餘的人也要餵馬,少俠縱然有絕世武功,但人在江湖,依然要講究江湖規矩——排隊。

dispatch__semaphore__create 建立時,傳入的數值,爲馬槽數量。

dispatch__semaphore__signal 至關於走了一匹馬。

dispatch__semaphore__wait 至關於來了一匹馬。

調用一次 dispatch__semaphore__signal 就走了一匹馬,調用一次 dispatch__semaphore__wait 就來了一匹馬,當馬槽的數量爲0時,再來的馬就只能等待了。若是有耐心能夠一直等,沒有耐心,等半個時辰也能夠走。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備畫方");
    sleep(2);
    NSLog(@"畫方");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備畫圓");
    sleep(2);
    NSLog(@"畫圓");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備吹口哨");
    sleep(2);
    NSLog(@"吹口哨");
    dispatch_semaphore_signal(semaphore);
});

//輸出

準備吹口哨
準備畫方
準備畫圓
畫圓
吹口哨
畫方
複製代碼

建立信號量值爲3,表明最多能夠三處資源同時訪問,當前有3個線程,因此當前沒有資源控制,能夠到看三個任務同時準備同時完成。

下面把信號量的值改成2

dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備畫方");
    sleep(2);
    NSLog(@"畫方");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備畫圓");
    sleep(2);
    NSLog(@"畫圓");
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"準備吹口哨");
    sleep(2);
    NSLog(@"吹口哨");
    dispatch_semaphore_signal(semaphore);
});

//輸出
準備畫圓
準備畫方
畫圓
畫方
準備吹口哨
吹口哨
複製代碼

建立信號量值爲2,表明最多能夠兩處資源同時訪問。但當前有三個線程,因此前面兩個任務 畫圓、畫方會先完成,以後再吹口哨。

勤加練習

少俠,到目前爲止,老夫已經給你講了GCD的四個基本心法,七個基本招式,這些都只是基本功,少俠往後必當好好練習,才能像更高的層次衝擊。老夫累了,這就閉關了,他日你練成神功,老夫定當出關,助你再佔高峯。

相關文章
相關標籤/搜索