什么是多線程?
計(jì)算機(jī)在運(yùn)行一段程序的時(shí)候,會(huì)把該程序的CPU命令列配置到內(nèi)存中乏苦,然后按照順序一個(gè)一個(gè)執(zhí)行命令列,這樣1個(gè)CPU執(zhí)行的CPU命令列為一條無(wú)分叉路徑就是線程尤筐。
而有多條這樣的執(zhí)行指令列的路徑存在時(shí)即為多線程汇荐。
iOS實(shí)現(xiàn)多線程有4種方法
- pthreads
- NSThread
- GCD
- NSOperation & NSOperationQueuef
這里我們主要講GCD
一、Dispatch Queue和線程的關(guān)系
什么是Dispatch Queue盆繁?
如其名稱掀淘,是執(zhí)行處理的等待隊(duì)列。當(dāng)我們通過(guò)dispatch_async等函數(shù)把Block加入Dispatch Queue后油昂,Dispatch Queue按照追加的順序(FIFO)執(zhí)行處理革娄。
Dispatch Queue的種類
- Serial Dispatch Queue(串行隊(duì)列) ——等待現(xiàn)在執(zhí)行中處理結(jié)束再加入隊(duì)列
- Concurrent Dispatch Queue(并發(fā)隊(duì)列) ——不等待現(xiàn)在執(zhí)行中處理結(jié)束,直接加入隊(duì)列
用代碼說(shuō)明:
Serial Dispatch Queue
dispatch_queue_t serial_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue, ^{
NSLog(@"block 1");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 2");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 3");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 4");
});
輸出如下:
2017-09-27 11:43:40.230126+0800 aegewgr[4327:1296458] block 1
2017-09-27 11:43:40.230335+0800 aegewgr[4327:1296458] block 2
2017-09-27 11:43:40.230461+0800 aegewgr[4327:1296458] block 3
2017-09-27 11:43:40.230548+0800 aegewgr[4327:1296458] block 4
這里Serial Dispatch Queue只會(huì)使用一個(gè)線程冕碟,因?yàn)樗谴嘘?duì)列拦惋,只會(huì)當(dāng)一個(gè)處理執(zhí)行完了才會(huì)將下一個(gè)任務(wù)交給線程處理。
Concurrent Dispatch Queue
dispatch_queue_t concurrent_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent_queue, ^{
NSLog(@"block 1");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 2");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 3");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 4");
});
輸出如下:
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304484] block 3
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304483] block 1
2017-09-27 11:45:09.057522+0800 aegewgr[4349:1304486] block 4
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304485] block 2
block的執(zhí)行完成
是隨機(jī)的安寺,因?yàn)樗麄冸m然是按順序把任務(wù)提交給線程厕妖,但是因?yàn)椴恍枰却耙粋€(gè)任務(wù)執(zhí)行,所以幾乎是同時(shí)交給線程處理的挑庶。所以這里會(huì)使用多個(gè)線程言秸,而具體線程數(shù)的多少由XNU內(nèi)核決定。
二迎捺、Dispatch Queue的使用
1井仰、獲取隊(duì)列
在使用Dispatch Queue的時(shí)候我們可以通過(guò)dispatch_queue_create
函數(shù)創(chuàng)建隊(duì)列,也可以獲取系統(tǒng)給我們提供的隊(duì)列破加。系統(tǒng)給我們提供了兩種隊(duì)列
2、同步與異步
- dispatch_async表示異步:將指定的Block”非同步“加入Dispatch Queue雹嗦,不做任何等待
- dispatch_sync表示同步:將指定的Block”同步“的加入Dispatch Queue范舀,在Block結(jié)束之前合是,dispatch_sync函數(shù)會(huì)一直等待
3、死鎖
由于dispatch_sync會(huì)等待Block執(zhí)行結(jié)束才會(huì)繼續(xù)往下執(zhí)行锭环,所以會(huì)產(chǎn)生死鎖的情況
我們直接在主線程中同步加入一個(gè)Blcok:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_sync(main_queue, ^{
NSLog(@"main queue");
});
NSLog(@"go on");
無(wú)任何輸出聪全,程序直接卡死了。這就是造成了死鎖辅辩。
因?yàn)樵撛创a在main_queue(主線程)中加入一個(gè)加入一個(gè)指定的Block难礼,并等待其執(zhí)行結(jié)束。而由于main_queue是一個(gè)串行隊(duì)列玫锋,它要等當(dāng)前線程中的任務(wù)處理完后才會(huì)把隊(duì)列中的任務(wù)提交到主線程蛾茉,而主線程又在等待這段代碼執(zhí)行,所以造成了相互等待撩鹿,就產(chǎn)生了死鎖谦炬。(而并發(fā)隊(duì)列不會(huì)產(chǎn)生死鎖)
如:
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_sync(global_queue, ^{
NSLog(@"global_queue out");
dispatch_sync(global_queue, ^{
NSLog(@"global_queue in");
});
});
輸出如下:
2017-09-27 16:11:56.332317+0800 aegewgr[4723:1590202] global_queue out
2017-09-27 16:11:56.332446+0800 aegewgr[4723:1590202] global_queue in
所以產(chǎn)生死鎖的話一般都是在串行隊(duì)列中并且是在一個(gè)線程中同步往這個(gè)線程提交一個(gè)Block。
4节沦、Dispatch Group(派發(fā)分組)
Dispatch Group是GCD的一項(xiàng)特性键思,能夠把任務(wù)分組。調(diào)用者在這組任務(wù)執(zhí)行完畢后會(huì)得到通知甫贯,并做相應(yīng)的處理吼鳞。
創(chuàng)建:dispatch_group_t group = dispatch_group_create();
同樣的,它也有
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, <#^(void)block#>)
和dispatch_sync
函數(shù)沒有什么區(qū)別叫搁,它只是多了一個(gè)dispatch_group_t參數(shù)赔桌,來(lái)把任務(wù)進(jìn)行分組。
還有一種方法能把任務(wù)加入dispatch_group常熙,那就是下面這對(duì)情侶:
dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave(dispatch_group_t group);
記住纬乍,這對(duì)情侶一定要成對(duì)出現(xiàn),dispatch_group_enter就是標(biāo)志下面的代碼要加入dispatch_group裸卫。dispatch_group_leave就是表示加入dispatch_group的代碼結(jié)束仿贬。也就是說(shuō)dispatch_group_enter和dispatch_group_leave之間的代碼就是加入dispatch_group中的。
說(shuō)了這么多墓贿,把一個(gè)隊(duì)列加入dispatch_group后有什么用呢茧泪?主要就是一組相似的操作結(jié)束后,你可以通過(guò)dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block)
函數(shù)來(lái)獲得通知聋袋,并進(jìn)行相應(yīng)的處理队伟。Block參數(shù)就是你要添加的處理。
當(dāng)然幽勒,如果你想設(shè)置一個(gè)等待時(shí)間嗜侮,可以使用dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
函數(shù),該函數(shù)設(shè)置了一個(gè)等待時(shí)間也就是說(shuō)程序要一直阻塞當(dāng)前線程直到group中的任務(wù)執(zhí)行完畢或者超過(guò)等待時(shí)間,才會(huì)繼續(xù)往下執(zhí)行锈颗。
而dispatch_block_notify
函數(shù)不會(huì)阻塞當(dāng)前線程顷霹,它只是指定了一個(gè)group任務(wù)執(zhí)行完后的回調(diào)。
需要舉個(gè)栗子嗎击吱?
好吧淋淀,還是舉個(gè)栗子吧。
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_group_t group = dispatch_group_create();
;
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 1");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 2");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 3");
});
dispatch_group_notify(group, global_queue, ^{
NSLog(@"notify");
});
NSLog(@"other task");
輸出如下:
2017-09-27 17:06:37.795564+0800 aegewgr[4983:1713915] other task
2017-09-27 17:06:37.795571+0800 aegewgr[4983:1714182] task 3
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714181] task 1
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714183] task 2
2017-09-27 17:06:37.795813+0800 aegewgr[4983:1714183] notify
可以看到dispatch_group_notify并沒有阻塞當(dāng)前線程覆醇,而且它提交的Block一定是當(dāng)group中的所有任務(wù)執(zhí)行完后才會(huì)執(zhí)行朵纷。另外,這里的queue可以不是一個(gè)queue永脓,你可以使用任意其它queue袍辞,不過(guò)最好是并發(fā)隊(duì)列,如果是串行隊(duì)列憨奸,任務(wù)會(huì)按順序一個(gè)一個(gè)執(zhí)行革屠,那使用group的意義就不大了。
看看dispatch_group_wait
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_group_t group = dispatch_group_create();
;
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 1");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 2");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 3");
});
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1));
NSLog(@"other task");
輸出如下:
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726567] task 2
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726568] task 1
2017-09-27 17:10:10.968140+0800 aegewgr[5002:1726569] task 3
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726443] other task
可以看到dispatch_group_wait函數(shù)阻塞了當(dāng)前線程排宰,只有當(dāng)group中的所有任務(wù)執(zhí)行完后線程才會(huì)繼續(xù)往下執(zhí)行似芝。
5 、其它相關(guān)函數(shù)
- dispatch_barrier_async和dispatch_barrier_sync(柵欄)
這兩個(gè)函數(shù)的作用差不多板甘,都是把它前面和它后面的函數(shù)分隔開党瓮。使它前面的任務(wù)先執(zhí)行,再執(zhí)行它添加的任務(wù)盐类,最后執(zhí)行它后面的任務(wù)寞奸。
那么它們有什么區(qū)別呢?
當(dāng)然從名字就能看出來(lái)在跳,就是提交任務(wù)的方式不同枪萄,一個(gè)是同步一個(gè)是異步,同步和異步的區(qū)別前面有解釋猫妙,如果忘了的話瓷翻,可以再回去看看。
- Dispatch Semaphore(信號(hào)量)
信號(hào)量其實(shí)就是用來(lái)保證訪問(wèn)資源的線程數(shù)割坠,當(dāng)信號(hào)量大于等于1時(shí)齐帚,資源可以訪問(wèn),否則無(wú)法訪問(wèn)資源彼哼,直到其它線程釋放資源对妄。
這里主要有三個(gè)函數(shù):
dispatch_semaphore_t dispatch_semaphore_create(long value); //創(chuàng)建一個(gè)dispatch_semaphore_t,value為初始信號(hào)量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); //信號(hào)量-1
long dispatch_semaphore_signal(dispatch_semaphore_t dsema); //信號(hào)量+1
怎么用呢敢朱?
還是舉個(gè)栗子吧:
假如有兩個(gè)資源剪菱,但是同時(shí)有三個(gè)線程想要訪問(wèn)摩瞎,就可以使用信號(hào)量進(jìn)行控制:
//crate的value表示,最多幾個(gè)資源可訪問(wèn)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任務(wù)1
dispatch_async(quene, ^{
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(quene, ^{
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(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
輸出如下:
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860224] run task 1
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860221] run task 2
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860224] complete task 1
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860221] complete task 2
2017-09-27 18:04:28.591386+0800 aegewgr[5149:1860219] run task 3
2017-09-27 18:04:29.591845+0800 aegewgr[5149:1860219] complete task 3
假如把信號(hào)量設(shè)置為3呢琅豆?
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
輸出如下:
2017-09-27 18:08:37.535634+0800 aegewgr[5169:1873722] run task 2
2017-09-27 18:08:37.535637+0800 aegewgr[5169:1873721] run task 1
2017-09-27 18:08:37.535636+0800 aegewgr[5169:1873723] run task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873723] complete task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873721] complete task 1
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873722] complete task 2
- dispatch_once
此函數(shù)在我們創(chuàng)建單例的時(shí)候經(jīng)常會(huì)用到愉豺,就是可以保證在應(yīng)用程序執(zhí)行中該函數(shù)只執(zhí)行一次。即使在多線程環(huán)境也茫因,也可以保證百分百的安全。
寫在文末
本來(lái)是打算寫一篇關(guān)于多線程的文章的杖剪,因?yàn)橄氚袵CD介紹的全面一點(diǎn)冻押,所以篇幅就有點(diǎn)長(zhǎng)了,另外三種方式請(qǐng)看這里盛嘿。