本人有若干成套學(xué)習(xí)視頻, 可試看! 可試看! 可試看, 重要的事情說(shuō)三遍 包含Java
, 數(shù)據(jù)結(jié)構(gòu)與算法
, iOS
, 安卓
, python
, flutter
等等, 如有需要, 聯(lián)系微信tsaievan
.
在GCD函數(shù)中, 我們常常碰到同步,異步,串行隊(duì)列,并發(fā)隊(duì)列,全局隊(duì)列,主隊(duì)列等概念,而這些概念又常常組合在一起, 十分頭疼, 這篇文章就來(lái)梳理一下這些煩人的概念.
不想看長(zhǎng)篇大論的, 直接看文章末尾的表格即可!
在此之前, GCD中還涉及到兩個(gè)十分重要的概念, 就是任務(wù)和隊(duì)列
- 任務(wù)(Task): 你需要執(zhí)行的操作
- 隊(duì)列(Queue): 存放任務(wù)的容器
GCD中兩個(gè)重要的函數(shù), 一個(gè)同步執(zhí)行, 一個(gè)異步執(zhí)行
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
這個(gè)函數(shù)中需要填入兩個(gè)參數(shù), 一個(gè)是隊(duì)列, 一個(gè)是任務(wù), 任務(wù)就是封裝在block代碼塊中的. 所以, 我們?cè)谑褂靡陨蟽蓚€(gè)函數(shù)時(shí), 只需要?jiǎng)?chuàng)建隊(duì)列, 以及把自己需要執(zhí)行的代碼封裝在block中就可以了
CGD中還給我們提供了兩個(gè)類似的函數(shù), 請(qǐng)看
dispatch_sync_f(dispatch_queue_t queue,
void *_Nullable context,
dispatch_function_t work);
dispatch_async_f(dispatch_queue_t queue,
void *_Nullable context,
dispatch_function_t work);
這兩個(gè)函數(shù)就沒(méi)有之前那兩個(gè)簡(jiǎn)單, 因?yàn)檫@兩個(gè)函數(shù)不是將任務(wù)封裝在block代碼塊中, 而是封裝在函數(shù)里
如下面的代碼所示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_async_f(dispatch_get_global_queue(0, 0), nil, task);
}
void task (void *param) {
NSLog(@"downloadTask-------%@",[NSThread currentThread]);
}
dispatch_async_f(_, _, _);
這個(gè)函數(shù)的三個(gè)參數(shù)我分別傳入
- 一個(gè)全局并發(fā)隊(duì)列
- 參數(shù)我傳空
- 定義一個(gè)返回值為空, 參數(shù)為void*類型的函數(shù), 然后把函數(shù)名作為參數(shù)傳進(jìn)去
在我定義的這個(gè)返回值為空, 參數(shù)為void*類型的函數(shù)中, 我把我需要操作的任務(wù)封裝在里面, 其實(shí)里面的代碼就跟之前block代碼塊里的代碼一模一樣. 也能起到同樣的效果
那么, 同步執(zhí)行和異步執(zhí)行有什么區(qū)別呢? 我們先來(lái)看看官方文檔的解釋
When a work item is executed synchronously with the** sync** method, the program waits until execution finishes before the method call returns
When a work item is executed asynchronously with the async method, the method call returns immediately
也就是說(shuō)
假如我有A,B,C三個(gè)任務(wù),
如果這三個(gè)任務(wù)都是同步執(zhí)行, 程序?qū)⒌却鼳 執(zhí)行完畢之后, 再執(zhí)行B, 再執(zhí)行C
如果這三個(gè)任務(wù)都是是異步執(zhí)行, 程序直接跳過(guò)A,B,C,執(zhí)行后面的代碼, 執(zhí)行完畢之后, 再來(lái)執(zhí)行A,B,C中的任務(wù)
另外, 還有一點(diǎn)需要明確的是
-
同步執(zhí)行沒(méi)有開(kāi)啟新線程的能力, 所有的任務(wù)都只能在當(dāng)前線程執(zhí)行
-
異步執(zhí)行有開(kāi)啟新線程的能力, 但是, 有開(kāi)啟新線程的能力, 也不一定會(huì)利用這種能力, 也就是說(shuō), 異步執(zhí)行是否開(kāi)啟新線程, 需要具體問(wèn)題具體分析
我們?cè)賮?lái)看一下串行隊(duì)列和并發(fā)隊(duì)列
無(wú)論任何隊(duì)列, 其實(shí)都遵循FIFO(first in first out, 先進(jìn)先出原則),
但是:
- 并發(fā)隊(duì)列中的任務(wù)會(huì)放到不同的線程中去執(zhí)行.
- 串行隊(duì)列中的任務(wù)只會(huì)放到同一線程中去執(zhí)行.
如下圖所示
那么同步執(zhí)行,異步執(zhí)行,并發(fā)隊(duì)列,串行隊(duì)列互相組合又會(huì)發(fā)生什么樣的情況呢? 這個(gè)時(shí)候, 就有四種情況需要分析了
- 情況1 : 異步執(zhí)行 + 并發(fā)隊(duì)列
/****************** -------- 異步執(zhí)行 + 并發(fā)隊(duì)列 -------- ******************/
- (void)asyncConcurrent {
/* 1. 創(chuàng)建一個(gè)并發(fā)隊(duì)列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_CONCURRENT);
/* 2. 將任務(wù)放到隊(duì)列中, 下面的代碼將三個(gè)任務(wù)放到隊(duì)列中 */
dispatch_async(concurrentQueue, ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download2-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download3-------%@",[NSThread currentThread]);
});
}
執(zhí)行情況
可以看出, 開(kāi)啟了不同的線程, 任務(wù)完成的順序也是隨機(jī)的
但是不同的任務(wù)都開(kāi)啟一個(gè)獨(dú)立的線程, 那我有100個(gè)任務(wù),會(huì)開(kāi)啟100條線程嗎? 答案是果斷不會(huì), 如下圖所示
可以看出的是, 任務(wù),10,11,12利用了之前的線程, 所以線程是不會(huì)無(wú)限開(kāi)啟的.
- 情況2 : 異步執(zhí)行 + 串行隊(duì)列
/****************** -------- 異步執(zhí)行 + 串行隊(duì)列 -------- ******************/
- (void)asyncSerial {
/* 1. 創(chuàng)建一個(gè)串行隊(duì)列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);
/* 2. 將不同的任務(wù)添加到隊(duì)列中 */
dispatch_async(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
可以看出的是: 異步執(zhí)行 + 串行隊(duì)列也開(kāi)啟了新的線程, 但是不管任務(wù)有多少個(gè), 異步執(zhí)行 + 同一條串行隊(duì)列只開(kāi)啟一條新的線程, 任務(wù)的執(zhí)行順序也是按照隊(duì)列中的順序執(zhí)行的, 因?yàn)橥粭l線程中, 必須等到前一個(gè)任務(wù)執(zhí)行完畢后, 才能執(zhí)行下一個(gè)任務(wù).
- 情況3 : 同步執(zhí)行+ 并發(fā)隊(duì)列
/****************** -------- 同步執(zhí)行 + 并發(fā)隊(duì)列 -------- ******************/
- (void)syncConcurrent {
/* 1. 創(chuàng)建一條并發(fā)隊(duì)列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_CONCURRENT);
/* 2. 把任務(wù)放到隊(duì)列中 */
dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
三個(gè)任務(wù)都在主線程中執(zhí)行, 并沒(méi)有開(kāi)啟新的線程. 但是, 是不是所有的同步執(zhí)行的操作都在主線程中執(zhí)行呢? 當(dāng)然不是. 看下面的代碼
- (void)syncConcurrentOnBackgroundThread {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self syncConcurrent];
});
}
我把同步執(zhí)行+ 并發(fā)隊(duì)列這個(gè)操作放到子線程去執(zhí)行, 那么執(zhí)行的線程就是子線程
所以說(shuō), 同步執(zhí)行+ 并發(fā)隊(duì)列, 并不會(huì)開(kāi)啟新的線程, 即使是并發(fā)隊(duì)列, 也然并卵
- 情況4 : 同步執(zhí)行+ 串行隊(duì)列
/****************** -------- 同步操作 + 串行隊(duì)列 -------- ******************/
- (void)syncSerial {
/* 1. 創(chuàng)建串行隊(duì)列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);
/* 2. 將任務(wù)放到隊(duì)列中 */
dispatch_sync(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
如果是在子線程中執(zhí)行同步串行隊(duì)列的操作, 當(dāng)前的線程就是子線程
總之, 需要記住的就是, 同步執(zhí)行并沒(méi)有開(kāi)啟子線程的能力, 所有的操作, 都是在當(dāng)前線程執(zhí)行.
故事到這里就結(jié)束了嗎? 并沒(méi)有
還有一個(gè)操蛋的全局并發(fā)隊(duì)列和主隊(duì)列, 這兩個(gè)又是什么鬼呢?
全局并發(fā)隊(duì)列就是我們常說(shuō)的全局隊(duì)列
首先, 它是一個(gè)并發(fā)隊(duì)列, 他是系統(tǒng)為我們創(chuàng)建好的一個(gè)全局的并發(fā)隊(duì)列, 所以, 有時(shí)候, 我們不需要自己創(chuàng)建一個(gè)并發(fā)隊(duì)列, 直接用系統(tǒng)為我們提供的全局隊(duì)列就可以了,所以全局隊(duì)列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊(duì)列是一樣的
比較特殊的是主隊(duì)列
系統(tǒng)會(huì)把主隊(duì)列中的任務(wù)放在主線程中執(zhí)行
- 情況5 : 異步執(zhí)行+ 主隊(duì)列
/****************** -------- 異步執(zhí)行 + 主隊(duì)列 -------- ******************/
- (void)asyncMainQueue {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download1------%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download2------%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download3------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
異步執(zhí)行雖然有開(kāi)啟新線程的能力, 但是異步執(zhí)行 + 主隊(duì)列并不會(huì)開(kāi)啟新的線程, 任務(wù)都是在主線程中執(zhí)行的
- 情況6 : 同步執(zhí)行+ 主隊(duì)列
/****************** -------- 同步執(zhí)行 + 主隊(duì)列 -------- ******************/
- (void)syncMainQueue {
NSLog(@"------start-------");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
NSLog(@"-------end---------");
}
執(zhí)行情況:
直接崩潰了, 以前這種情況是會(huì)發(fā)生死鎖的, 不知道是不是因?yàn)槭荴Code8.2的原因, 現(xiàn)在直接報(bào)錯(cuò). 那么, 為什么會(huì)發(fā)生這種情況呢?
可以這樣理解, 上圖中, 執(zhí)行syncMainQueue這個(gè)方法是在主線程中執(zhí)行的, 你可以把它看做一個(gè)任務(wù)A, 這個(gè)任務(wù)A也是在主隊(duì)列中的,那么代碼執(zhí)行到第179行的時(shí)候, 啟動(dòng)了任務(wù)B, 把任務(wù)B放進(jìn)了主隊(duì)列中, 由于是同步執(zhí)行, 所以, 必須等待任務(wù)B執(zhí)行完了之后才能繼續(xù)向下執(zhí)行, 但是主線程有任務(wù)A, 所以任務(wù)B無(wú)法放到主線程中去執(zhí)行,任務(wù)B等待任務(wù)A執(zhí)行, 任務(wù)A等待任務(wù)B執(zhí)行, 這樣就造成了死鎖.
如圖所示:
但是, 如果將同步執(zhí)行+ 主隊(duì)列的操作放到子線程中執(zhí)行, 就不會(huì)造成死鎖
那為什么同步執(zhí)行 + 串行隊(duì)列不會(huì)造成死鎖呢?
同步執(zhí)行是不會(huì)開(kāi)啟新的線程的, 如果當(dāng)前線程是主線程, 則任務(wù)在主線程中執(zhí)行. 如下圖所示
說(shuō)了這么多, 可能又有點(diǎn)暈, 其實(shí)這些應(yīng)該在實(shí)際開(kāi)發(fā)中慢慢體會(huì), 碰到的情況多了, 自然而然就明白了. 現(xiàn)在, 我們只要記住下面的表格就可以了
各種隊(duì)列的執(zhí)行效果圖:
源碼奉上, 大家自己寫(xiě)了試試, 測(cè)試一下, 然后應(yīng)該很容易就理解了
源碼
有任何問(wèn)題, 歡迎留言交流討論, 謝謝!