1.信號(hào)量的簡介
GCD的信號(hào)量主要涉及的函數(shù)有以下三個(gè)
//創(chuàng)建一個(gè)dispatch_semaphore_t類型的信號(hào)量,value是信號(hào)量的初始值
dispatch_semaphore_create(long value);
//等待信號(hào)量
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//發(fā)送信號(hào)量
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待信號(hào)量。如果信號(hào)量值為0,那么該函數(shù)就會(huì)一直等待,也就是不返回(相當(dāng)于阻塞當(dāng)前線程),直到該函數(shù)等待的信號(hào)量的值大于等于1,該函數(shù)會(huì)對信號(hào)量的值進(jìn)行減1操作,然后返回。
第二個(gè)參數(shù)傳入的是dispatch_time_t,表示過了這個(gè)時(shí)間就不再等待
dispatch_semaphore_signal(dispatch_semaphore_t deem);
發(fā)送信號(hào)量。該函數(shù)會(huì)對信號(hào)量的值進(jìn)行加1操作。
2.信號(hào)量的用法
- 1.創(chuàng)建信號(hào)量,創(chuàng)建時(shí)設(shè)置信號(hào)量的初始值
- 2.在任務(wù)開始前添加dispatch_semaphore_wait函數(shù),使任務(wù)等待(阻塞)
- 3.在任務(wù)結(jié)束后調(diào)用dispatch_semaphore_signal函數(shù),釋放一個(gè)信號(hào)量
信號(hào)量的dispatch_semaphore_wait和dispatch_semaphore_signal通常是成對出現(xiàn)的。
在當(dāng)前任務(wù)執(zhí)行之前,dispatch_semaphore_wait函數(shù)會(huì)判斷當(dāng)前的信號(hào)量是否大于等于1,若大于等于1,則會(huì)執(zhí)行任務(wù),并且把信號(hào)量減1,當(dāng)任務(wù)完成后調(diào)用dispatch_semaphore_signal函數(shù),信號(hào)量加1,否則會(huì)阻塞當(dāng)前任務(wù),當(dāng)dispatch_semaphore_signal函數(shù)被調(diào)用時(shí),信號(hào)量會(huì)加1,此時(shí)被阻塞的任務(wù)會(huì)繼續(xù)進(jìn)行。
3.使用信號(hào)量控制并發(fā)隊(duì)列異步執(zhí)行時(shí)的線程數(shù)量
以下列子中使用的都是并發(fā)隊(duì)列,串行隊(duì)列沒有控制線程數(shù)量的意義,串行隊(duì)列只會(huì)開辟一個(gè)子線程。
3.1異步執(zhí)行并發(fā)隊(duì)列控制子線程數(shù)最多為兩個(gè)
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
sleep(1);
NSLog(@"任務(wù)%d 結(jié)束執(zhí)行 當(dāng)前線程:%@", i , [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"主線程代碼");
運(yùn)行結(jié)果:
2020-02-29 11:16:21.441042+0800 Test[27081:1957822] 主線程代碼
2020-02-29 11:16:22.446368+0800 Test[27081:1957912] 任務(wù)1 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x600003ce8700>{number = 3, name = (null)}
2020-02-29 11:16:22.446400+0800 Test[27081:1957911] 任務(wù)0 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x600003ce8380>{number = 4, name = (null)}
2020-02-29 11:16:23.448882+0800 Test[27081:1957904] 任務(wù)2 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x600003cee5c0>{number = 5, name = (null)}
2020-02-29 11:16:23.448926+0800 Test[27081:1957924] 任務(wù)4 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x600003ce8a80>{number = 6, name = (null)}
2020-02-29 11:16:24.449496+0800 Test[27081:1957910] 任務(wù)3 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x600003ce9800>{number = 7, name = (null)}
在上述代碼中,首先創(chuàng)建一個(gè)初始值為2的信號(hào)量,當(dāng)子線程中的任務(wù)開始運(yùn)行時(shí)調(diào)用dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);,判斷信號(hào)量是否大于等于1,若大于等于1則繼續(xù)執(zhí)行,否則阻塞當(dāng)前子線程。若執(zhí)行了任務(wù)則信號(hào)量減1,執(zhí)行完釋放信號(hào)量。
因?yàn)閐ispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);是在子線程中調(diào)用的,所以主線程之后的任務(wù)仍然可以并發(fā)執(zhí)行。并且異步開啟的五個(gè)任務(wù)不是按順序執(zhí)行的。
所以就達(dá)到了控制子線程數(shù)最多為兩個(gè)的效果
把信號(hào)量的初始值設(shè)置為1,就可以達(dá)到一次只執(zhí)行一個(gè)任務(wù)的效果,和串行隊(duì)列的區(qū)別是任務(wù)可以在不同的子線程執(zhí)行??梢猿浞职l(fā)揮多線程的優(yōu)勢。(可以把上面例子中創(chuàng)建信號(hào)量時(shí)的值從2改為1,可以發(fā)現(xiàn)會(huì)隔一秒執(zhí)行一個(gè)任務(wù),在不同線程執(zhí)行,執(zhí)行先后順序也不固定)
3.2把dispatch_semaphore_wait寫到子線程之外
如果把dispatch_semaphore_wait寫到子線程之外,此時(shí)會(huì)阻塞主線程,相當(dāng)于信號(hào)量控制的是最多可以往隊(duì)列添加任務(wù)的數(shù)量,此時(shí)如果信號(hào)量為1,任務(wù)會(huì)一個(gè)一個(gè)按順序在多個(gè)線程執(zhí)行,最后一個(gè)等待函數(shù)通過之后再執(zhí)行主線程的代碼,因?yàn)閐ispatch_semaphore_wait阻塞的是主線程
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 5; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"任務(wù)%d 結(jié)束執(zhí)行 當(dāng)前線程:%@", i , [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"主線程代碼");
運(yùn)行結(jié)果:
2020-02-29 15:50:47.321348+0800 Test[29239:2124482] 任務(wù)0 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x60000365d400>{number = 3, name = (null)}
2020-02-29 15:50:48.325445+0800 Test[29239:2124482] 任務(wù)1 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x60000365d400>{number = 3, name = (null)}
2020-02-29 15:50:49.328935+0800 Test[29239:2124482] 任務(wù)2 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x60000365d400>{number = 3, name = (null)}
2020-02-29 15:50:50.333874+0800 Test[29239:2124482] 任務(wù)3 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x60000365d400>{number = 3, name = (null)}
2020-02-29 15:50:50.334178+0800 Test[29239:2124228] 主線程代碼
2020-02-29 15:50:51.338454+0800 Test[29239:2124482] 任務(wù)4 結(jié)束執(zhí)行 當(dāng)前線程:<NSThread: 0x60000365d400>{number = 3, name = (null)}
由運(yùn)行結(jié)果可以知道:
主線程等任務(wù)3結(jié)束之后馬上就執(zhí)行了,因?yàn)榇藭r(shí)最后一個(gè)等待函數(shù)已經(jīng)釋放。
任務(wù)是按順序一個(gè)一個(gè)執(zhí)行的,因?yàn)樵诎讶蝿?wù)添加到隊(duì)列之前已經(jīng)阻塞,所以不會(huì)并發(fā)。
由于添加的任務(wù)太少,sleep函數(shù)傳入的時(shí)間太長,所以當(dāng)前顯示的是都在同一個(gè)子線程執(zhí)行的,把sleep函數(shù)沉睡的時(shí)間改為0.001,子線程添加100個(gè)任務(wù),此時(shí)可以看到會(huì)在不同子線程執(zhí)行任務(wù),說明可以充分利用CPU的多線程優(yōu)勢
4.隊(duì)列組和信號(hào)量的組合使用
GCD-隊(duì)列組
假設(shè)有一個(gè)需求:要并發(fā)執(zhí)行兩個(gè)任務(wù),并且在每個(gè)任務(wù)中請求一次網(wǎng)絡(luò),等兩次請求都完成后再去執(zhí)行隊(duì)列組監(jiān)聽回調(diào)中的任務(wù)。
4.1如果不使用信號(hào)量,只用隊(duì)列組,達(dá)不到效果的用法
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)1");
sleep(1);
dispatch_async(queue, ^{
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束");
});
NSLog(@"結(jié)束任務(wù)1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)2");
sleep(1);
dispatch_async(queue, ^{
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束");
});
NSLog(@"結(jié)束任務(wù)2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"回調(diào)中的任務(wù),需要最后執(zhí)行");
});
運(yùn)行結(jié)果:
2020-02-29 16:17:11.362046+0800 Test[29549:2162983] 開始任務(wù)2
2020-02-29 16:17:11.362046+0800 Test[29549:2162991] 開始任務(wù)1
2020-02-29 16:17:12.366653+0800 Test[29549:2162983] 結(jié)束任務(wù)2
2020-02-29 16:17:12.366694+0800 Test[29549:2162992] 任務(wù)2中的網(wǎng)絡(luò)請求開始
2020-02-29 16:17:12.366634+0800 Test[29549:2162991] 結(jié)束任務(wù)1
2020-02-29 16:17:12.366737+0800 Test[29549:2162993] 任務(wù)1中的網(wǎng)絡(luò)請求開始
2020-02-29 16:17:12.366936+0800 Test[29549:2162983] 回調(diào)中的任務(wù),需要最后執(zhí)行
2020-02-29 16:17:14.368896+0800 Test[29549:2162992] 任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:17:14.368895+0800 Test[29549:2162993] 任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束
可以發(fā)現(xiàn)如果只用了隊(duì)列組的方式,回調(diào)中的任務(wù)會(huì)在網(wǎng)絡(luò)請求結(jié)束前就調(diào)用,因?yàn)榫W(wǎng)絡(luò)請求用的也是異步的,所以不會(huì)阻塞線程,隊(duì)列組監(jiān)聽的任務(wù)1,2會(huì)在網(wǎng)絡(luò)請求返回前就已經(jīng)結(jié)束。
4.2只用隊(duì)列組,完成需求的方法
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)1");
sleep(1);
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束");
});
NSLog(@"結(jié)束任務(wù)1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)2");
sleep(1);
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束");
});
NSLog(@"結(jié)束任務(wù)2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"回調(diào)中的任務(wù),需要最后執(zhí)行");
});
執(zhí)行結(jié)果:
2020-02-29 16:20:42.986496+0800 Test[29599:2168460] 開始任務(wù)2
2020-02-29 16:20:42.986496+0800 Test[29599:2168459] 開始任務(wù)1
2020-02-29 16:20:43.986974+0800 Test[29599:2168459] 結(jié)束任務(wù)1
2020-02-29 16:20:43.986987+0800 Test[29599:2168460] 結(jié)束任務(wù)2
2020-02-29 16:20:43.987026+0800 Test[29599:2168465] 任務(wù)2中的網(wǎng)絡(luò)請求開始
2020-02-29 16:20:43.987046+0800 Test[29599:2168453] 任務(wù)1中的網(wǎng)絡(luò)請求開始
2020-02-29 16:20:45.988794+0800 Test[29599:2168453] 任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:20:45.988794+0800 Test[29599:2168465] 任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:20:45.989003+0800 Test[29599:2168453] 回調(diào)中的任務(wù),需要最后執(zhí)行
這種寫法相當(dāng)于在任務(wù)1和2的代碼最后,把他們各自的網(wǎng)絡(luò)請求任務(wù)又加入了隊(duì)列組,因?yàn)榇藭r(shí)隊(duì)列組中的任務(wù)還沒完成,所以不會(huì)進(jìn)回調(diào),隊(duì)列組中又被新加任務(wù),所以需要等任務(wù)組中的任務(wù)都完成,才會(huì)執(zhí)行回調(diào)。
4.3用隊(duì)列組的dispatch_group_enter和dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)1");
sleep(1);
dispatch_async(queue, ^{
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束");
dispatch_group_leave(group);
});
NSLog(@"結(jié)束任務(wù)1");
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務(wù)2");
sleep(1);
dispatch_async(queue, ^{
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束");
dispatch_group_leave(group);
});
NSLog(@"結(jié)束任務(wù)2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"回調(diào)中的任務(wù),需要最后執(zhí)行");
});
執(zhí)行結(jié)果:
2020-02-29 16:28:28.805932+0800 Test[29727:2180149] 開始任務(wù)1
2020-02-29 16:28:28.805932+0800 Test[29727:2180146] 開始任務(wù)2
2020-02-29 16:28:29.808428+0800 Test[29727:2180146] 結(jié)束任務(wù)2
2020-02-29 16:28:29.808428+0800 Test[29727:2180149] 結(jié)束任務(wù)1
2020-02-29 16:28:29.808512+0800 Test[29727:2180155] 任務(wù)1中的網(wǎng)絡(luò)請求開始
2020-02-29 16:28:29.808556+0800 Test[29727:2180150] 任務(wù)2中的網(wǎng)絡(luò)請求開始
2020-02-29 16:28:31.813638+0800 Test[29727:2180155] 任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:28:31.813638+0800 Test[29727:2180150] 任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:28:31.814062+0800 Test[29727:2180155] 回調(diào)中的任務(wù),需要最后執(zhí)行
dispatch_group_enter表示進(jìn)入了這個(gè)隊(duì)列組,dispatch_group_leave表示離開這個(gè)隊(duì)列組,dispatch_group_leave在任務(wù)1,2的網(wǎng)絡(luò)請求結(jié)束之后再調(diào)用,所以被回調(diào)時(shí)已經(jīng)結(jié)束了所有任務(wù)
4.4隊(duì)列組和信號(hào)量組合使用
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)1");
sleep(1);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"結(jié)束任務(wù)1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"開始任務(wù)2");
sleep(1);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求開始");
sleep(2);
NSLog(@"任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"結(jié)束任務(wù)2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"回調(diào)中的任務(wù),需要最后執(zhí)行");
});
執(zhí)行結(jié)果:
2020-02-29 16:34:46.148453+0800 Test[29835:2191521] 開始任務(wù)1
2020-02-29 16:34:46.148486+0800 Test[29835:2191528] 開始任務(wù)2
2020-02-29 16:34:47.151338+0800 Test[29835:2191527] 任務(wù)2中的網(wǎng)絡(luò)請求開始
2020-02-29 16:34:47.151341+0800 Test[29835:2191529] 任務(wù)1中的網(wǎng)絡(luò)請求開始
2020-02-29 16:34:49.152982+0800 Test[29835:2191527] 任務(wù)2中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:34:49.152982+0800 Test[29835:2191529] 任務(wù)1中的網(wǎng)絡(luò)請求結(jié)束
2020-02-29 16:34:49.153431+0800 Test[29835:2191521] 結(jié)束任務(wù)1
2020-02-29 16:34:49.153438+0800 Test[29835:2191528] 結(jié)束任務(wù)2
2020-02-29 16:34:49.153697+0800 Test[29835:2191528] 回調(diào)中的任務(wù),需要最后執(zhí)行
代碼解析:
1.在任務(wù)1、2的網(wǎng)絡(luò)請求任務(wù)開始前新建一個(gè)初始值為0的信號(hào)量(dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);)
2.因?yàn)榫W(wǎng)絡(luò)請求任務(wù)是異步執(zhí)行的,所以任務(wù)1、2不會(huì)管網(wǎng)絡(luò)請求是否完成,直接執(zhí)行網(wǎng)絡(luò)請求代碼塊之后的代碼,此時(shí)阻塞任務(wù)1、2所在的線程(dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);)
3.在網(wǎng)絡(luò)請求完成之后釋放信號(hào)量(dispatch_semaphore_signal(semaphore);),此時(shí)信號(hào)量從0變成1,任務(wù)1、2可以繼續(xù)往下執(zhí)行(),任務(wù)1、2執(zhí)行完成之后dispatch_group_notify收到回調(diào),執(zhí)行里面的代碼。