前言
本文主要介紹 GCD 中的線程組 Group,不講 GCD 基礎概念知識。如果對 GCD 的基本知識點不是很清楚的話,建議去補充一下。好了,廢話不多說,坐穩了,馬上就開車了。
正文
線程組
GCD 為我們提供了 dispatch_group 方法,它有一個組的概念,可以把多個任務歸并到一個組內來執行,通過監聽組內所有任務的執行情況來做相應處理。
1.線程組內的任務是同步的。
假設我們現在有兩個任務,任務 1 和任務 2,任務 1:for 循環 1000 次,任務 2:for 循環 100 次。等到任務 1 和任務 2 都執行完后再執行回調。
上代碼。
// 創建一個group
dispatch_group_t group = dispatch_group_create();
// 創建一個隊列:全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 將任務1添加到 group 中
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"任務1-----%d",i);
}
});
// 將任務2添加到 group 中
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"任務2-----%d",i);
}
});
// 任務1和任務2執行結束,回調
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成任務");
});
});
打印結果
2016-12-16 12:31:34.723 Group[12091:514693] 任務1-----0
2016-12-16 12:31:34.723 Group[12091:514681] 任務2-----0
2016-12-16 12:31:34.723 Group[12091:514693] 任務1-----1
2016-12-16 12:31:34.723 Group[12091:514681] 任務2-----1
2016-12-16 12:31:34.723 Group[12091:514693] 任務1-----2
2016-12-16 12:31:34.723 Group[12091:514681] 任務2-----2
····
····
····
2016-12-16 12:31:35.105 Group[12091:514693] 任務1-----997
2016-12-16 12:31:35.105 Group[12091:514693] 任務1-----998
2016-12-16 12:31:35.105 Group[12091:514693] 任務1-----999
2016-12-16 12:31:35.106 Group[12091:514347] 完成任務
從打印結果可以看出:
先并發執行任務 1 和任務 2,任務 2 首先完成,然后任務 1 還在執行,任務 1 執行結束后,再執行回調。
2.線程組內的任務是異步的。
實際開發中我們可能有這樣的需求:并發請求多個網絡接口,等到所有的接口請求結束之后,我們再來個回調刷新 TableView。
這里面有些同學可能會說把前面例子里的任務 1 和 任務 2 改為網絡請求就可以了。那好,那我們來試試。
上代碼。
// 創建一個group
dispatch_group_t group = dispatch_group_create();
// 創建一個隊列:全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 將任務1添加到 group 中
dispatch_group_async(group, queue, ^{
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"任務1-----%d",i);
}
});
});
// 將任務2添加到 group 中
dispatch_group_async(group, queue, ^{
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"任務2-----%d",i);
}
});
});
// 任務1和任務2執行結束,回調
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成任務");
});
});
打印結果
2016-12-16 13:12:40.541 Group[12636:545117] 任務1-----0
2016-12-16 13:12:40.541 Group[12636:544738] 完成任務
2016-12-16 13:12:40.541 Group[12636:545118] 任務2-----0
2016-12-16 13:12:40.541 Group[12636:545117] 任務1-----1
2016-12-16 13:12:40.541 Group[12636:545118] 任務2-----1
2016-12-16 13:12:40.542 Group[12636:545117] 任務1-----2
2016-12-16 13:12:40.542 Group[12636:545118] 任務2-----2
····
····
····
2016-12-16 13:12:40.876 Group[12636:545117] 任務1-----997
2016-12-16 13:12:40.876 Group[12636:545117] 任務1-----998
2016-12-16 13:12:40.877 Group[12636:545117] 任務1-----999
從打印結果可以看出:
回調并沒有等到任務 1 和任務 2 執行完就打印了,怎么跟我們想得不一樣呢?
好,那我下面來解釋一下。
- 第一個例子中,任務 1 是同步的任務,任務 2 也是同步的任務。
- 第二個例子中,任務 1 是異步的任務,任務 2 也是異步的任務。
同步和異步的最大區別是:同步是一個一個的執行,會有一個等待。而異步則不是,它不會等待。
因為 dispatch_group_async 里面的任務是異步的,所以任務在執行的時候,它不會去等待 for 循環執行結束,它會直接跳過 dispatch_async 這 block 執行下一句去了,所以 dispatch_group_notify 也會很快就執行。
下面再看下如何去解決這個問題吧。
這邊就用到了 dispatch_group_enter 和 dispatch_group_leave。它們兩個是成對出現的。dispatch_group_enter 使 group 里正要執行的任務數遞增,dispatch_group_leave 則使之遞減。所以調用完 dispatch_group_enter 以后,必須有與之對應的 dispatch_group_leave 才行。如果調用 dispatch_group_enter 之后,沒有相應的 dispatch_group_leave 操作,那么這一組任務就永遠執行不完。在使用時,可以在向隊列中添加任務時調用 dispatch_group_enter,在任務執行完成之后合適的地方調用 dispatch_group_leave。
上代碼
// 創建一個group
dispatch_group_t group = dispatch_group_create();
// 創建一個隊列:全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 進入group
dispatch_group_enter(group);
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"任務1-----%d",i);
}
// 離開group
dispatch_group_leave(group);
});
// 進入group
dispatch_group_enter(group);
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"任務2-----%d",i);
}
// 離開group
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成任務");
});
});
打印結果
2016-12-16 13:15:17.945 Group[12870:559844] 任務1-----0
2016-12-16 13:15:17.945 Group[12870:559831] 任務2-----0
2016-12-16 13:15:17.945 Group[12870:559844] 任務1-----1
2016-12-16 13:15:17.945 Group[12870:559831] 任務2-----1
2016-12-16 13:15:17.945 Group[12870:559844] 任務1-----2
2016-12-16 13:15:17.946 Group[12870:559831] 任務2-----2
····
····
····
2016-12-16 13:15:18.375 Group[12870:559844] 任務1-----997
2016-12-16 13:15:18.376 Group[12870:559844] 任務1-----998
2016-12-16 13:15:18.376 Group[12870:559844] 任務1-----999
2016-12-16 13:15:18.376 Group[12870:559491] 完成任務
從打印結果可以看出:
和例子一的打印結果一樣,OK,問題解決。
其實還有一個方法也可以解決,信號量 dispatch_semaphore。
什么是信號量?
引用網友舉的一個例子:
以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知后,打開車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。
抽象的來講,信號量的特性如下:信號量是一個非負整數(車位數),所有通過它的線程/進程(車輛)都會將該整數減一(通過它當然是為了使用資源),當該整數值為零時,所有試圖通過它的線程都將處于等待狀態。在信號量上我們定義兩種操作: Wait(等待) 和 Release(釋放)。當一個線程調用Wait操作時,它要么得到資源然后將信號量減一,要么一直等下去(指放入阻塞隊列),直到信號量大于等于一時。Release(釋放)實際上是在信號量上執行加操作,對應于車輛離開停車場,該操作之所以叫做“釋放”是因為釋放了由信號量守護的資源。
在 GCD 中有三個函數是 semaphore 的操作,分別是:
// 創建一個semaphore
dispatch_semaphore_create(long value);
// 發送一個信號
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
// 等待信號
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
上代碼
// 創建一個group
dispatch_group_t group = dispatch_group_create();
// 創建一個隊列:全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 創建信號量,并且設置值為0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 將任務1添加到 group 中
dispatch_group_async(group, queue, ^{
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"任務1-----%d",i);
}
// 每次發送信號則 semaphore 會 +1
dispatch_semaphore_signal(semaphore);
});
// 由于是異步執行的,當 semaphore 等于 0,則會阻塞當前線程,直到執行了 block 的 dispatch_semaphore_signal,semaphore + 1,才會繼續執行。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
// 將任務2添加到 group 中
dispatch_group_async(group, queue, ^{
// 模擬異步網絡請求
dispatch_async(queue, ^{
for (int i = 0; i < 100; i++) {
NSLog(@"任務2-----%d",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成任務");
});
});
由于是異步執行的,當 semaphore 等于 0,則會阻塞當前線程,直到執行了 block 的 dispatch_semaphore_signal,semaphore + 1,才會繼續執行。這樣很好的解決了這個問題。
2016-12-16 13:18:11.747 Group[13276:586281] 任務1-----0
2016-12-16 13:18:11.747 Group[13276:586296] 任務2-----0
2016-12-16 13:18:11.747 Group[13276:586281] 任務1-----1
2016-12-16 13:18:11.747 Group[13276:586296] 任務2-----1
2016-12-16 13:18:11.747 Group[13276:586281] 任務1-----2
2016-12-16 13:18:11.748 Group[13276:586296] 任務2-----2
····
····
····
2016-12-16 13:18:12.111 Group[13276:586281] 任務1-----997
2016-12-16 13:18:12.111 Group[13276:586281] 任務1-----998
2016-12-16 13:18:12.111 Group[13276:586281] 任務1-----999
2016-12-16 13:18:12.111 Group[13276:585910] 完成任務
從打印結果可以看出:
和例子一的打印結果還是一樣。
上面兩個方法都可以,可能還有更多的方法。筆者暫時只知道這兩個方法,如果有大神知道還有其他的方法,可以聯系我,一起交流。
最后
由于筆者水平有限,文中如果有錯誤的地方,還望大神指出。或者有更好的方法和建議,我們可以一起交流。
附上本文的所有 demo 下載鏈接【GitHub】,配合 demo 一起看文章,效果會更佳。