GCD中涉及到兩個十分重要的概念, 就是任務(wù)和隊列
- 任務(wù)(Task): 你需要執(zhí)行的操作
- 隊列(Queue): 存放任務(wù)的容器
GCD中兩個重要的函數(shù), 一個同步執(zhí)行, 一個異步執(zhí)行
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
這兩個函數(shù)中均需要填入兩個參數(shù), 一個是隊列, 一個是任務(wù), 任務(wù)就是封裝在block代碼塊中的. 所以, 我們在使用以上兩個函數(shù)時, 只需要創(chuàng)建隊列,將任務(wù)(block)添加進指定的隊列中。并根據(jù)是否為sync決定調(diào)用該函數(shù)的線程是否需要阻塞辣苏。
注意:
這里調(diào)用該函數(shù)的線程并不執(zhí)行參數(shù)中指定的任務(wù)(block塊)檐晕,任務(wù)的執(zhí)行者是GCD分配給任務(wù)所在隊列的線程。調(diào)用dispatch_sync和dispatch_async的線程,并不一定是任務(wù)(block塊)的執(zhí)行者雏胃。
同步執(zhí)行和異步執(zhí)行有什么區(qū)別呢?
同步執(zhí)行:比如這里的dispatch_sync婚度,這個函數(shù)會把一個block加入到指定的隊列中,而且會一直等到執(zhí)行完blcok裹芝,這個函數(shù)才返回。因此在block執(zhí)行完之前娜汁,調(diào)用dispatch_sync方法的線程是阻塞的嫂易。
異步執(zhí)行:一般使用dispatch_async,這個函數(shù)也會把一個block加入到指定的隊列中掐禁,但是和同步執(zhí)行不同的是怜械,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了。
另外, 還有一點需要明確的是
- 同步執(zhí)行沒有開啟新線程的能力, 所有的任務(wù)都只能在當前線程執(zhí)行
- 異步執(zhí)行有開啟新線程的能力, 但是, 有開啟新線程的能力, 也不一定會利用這種能力, 也就是說, 異步執(zhí)行是否開啟新線程, 需要具體問題具體分析
我們再來看看串行隊列和并發(fā)隊列
無論任何隊列, 其實都遵循FIFO(first in first out, 先進先出原則)
串行隊列可以保證在執(zhí)行某個任務(wù)時穆桂,在它前面進入隊列的所有任務(wù)肯定執(zhí)行完了宫盔。對于每一個不同的串行隊列,系統(tǒng)會為這個隊列建立唯一的線程來執(zhí)行代碼享完。
但是并發(fā)隊列它們的執(zhí)行結(jié)束時間是不確定的灼芭,取決于每個任務(wù)的耗時。并發(fā)隊列中的任務(wù):GCD會動態(tài)分配多條線程來執(zhí)行般又。具體幾條線程取決于當前內(nèi)存使用狀況彼绷,線程池中線程數(shù)等因素。
清楚了以上概念,下面我們來根據(jù)他們的互相組合會發(fā)生什么,讓我們拭目以待
1.同步執(zhí)行+ 串行隊列
- (void)syncSerial{
/* 1.創(chuàng)建一個串行隊列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
/* 2.將任務(wù)放在隊列中 */
dispatch_sync(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
download1--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download2--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download3--------<NSThread: 0x600003536cc0>{number = 1, name = main}
可以看出的是: 同步執(zhí)行 + 串行隊列沒有開啟新的線程, 所有的操作, 都是在當前線程執(zhí)行.如果是在子線程中執(zhí)行同步串行隊列的操作, 當前的線程就是子線程,如下.
download1--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)三
2.同步執(zhí)行+ 并發(fā)隊列
- (void)syncConcurrent{
/* 1.創(chuàng)建一個并發(fā)隊列 */
dispatch_queue_t cuncurrentQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_CONCURRENT);
/* 2.將不同的任務(wù)添加到隊列中 */
dispatch_sync(cuncurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
NSLog(@"執(zhí)行完任務(wù)一");
dispatch_sync(cuncurrentQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
NSLog(@"執(zhí)行完任務(wù)二");
dispatch_sync(cuncurrentQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
NSLog(@"執(zhí)行完任務(wù)三");
}
執(zhí)行情況:
download1--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)三
可以看出的是:三個任務(wù)都在主線程中執(zhí)行, 并沒有開啟新的線程. 但是, 是不是所有的同步執(zhí)行的操作都在主線程中執(zhí)行呢? 當然不是. 看下面的代碼
- (void)syncConcurrentOnBackgroundThread {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self syncConcurrent];
});
}
同理:我把同步執(zhí)行+ 并發(fā)隊列這個操作也放到子線程去執(zhí)行, 那么執(zhí)行的線程就是子線程
download1--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)三
3.異步執(zhí)行+ 串行隊列
- (void)asyncSerial{
/* 1.創(chuàng)建一個串行隊列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
/* 2.將不同的任務(wù)添加到隊列中 */
dispatch_async(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
download1--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}
download2--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}
download3--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}
可以看出的是: 異步執(zhí)行 + 串行隊列也開啟了新的線程, 但是不管任務(wù)有多少個, 異步執(zhí)行 + 同一條串行隊列只開啟一條新的線程, 任務(wù)的執(zhí)行順序也是按照隊列中的順序執(zhí)行的, 因為同一條線程中, 必須等到前一個任務(wù)執(zhí)行完畢后, 才能執(zhí)行下一個任務(wù).
4.異步執(zhí)行+ 并發(fā)隊列
- (void)asyncConcurrent{
/* 1.創(chuàng)建一個并發(fā)隊列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_CONCURRENT);
/* 2.將任務(wù)放到隊列中, 下面的代碼將三個任務(wù)放到隊列中 */
dispatch_async(concurrentQueue, ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download2-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download3-------%@",[NSThread currentThread]);
});
}
執(zhí)行情況:
執(zhí)行完任務(wù)一
download3-------<NSThread: 0x600003d420c0>{number = 5, name = (null)}
download2-------<NSThread: 0x600003d42000>{number = 4, name = (null)}
download1-------<NSThread: 0x600003d5b840>{number = 3, name = (null)}
可以看出的是:開啟了不同的線程, 且,但是線程也不會無限開啟,比如說你有100個任務(wù),會開啟100條線程嗎?
除了以上這幾類組合,還有兩個概念,一個是全局并發(fā)隊列和主隊列
全局并發(fā)隊列:就是我們常說的全局隊列,它是一個并發(fā)隊列, 它是系統(tǒng)為我們創(chuàng)建好的一個全局并發(fā)隊列, 所以, 有時候, 我們不需要自己創(chuàng)建一個并發(fā)隊列, 直接用系統(tǒng)為我們提供的全局隊列就可以了,所以全局隊列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊列是一樣的
比較特殊的是主隊列
主隊列:系統(tǒng)會把主隊列中的任務(wù)放在主線程中執(zhí)行
5.異步執(zhí)行+ 主隊列
- (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í)行情況:
download1------<NSThread: 0x600001906780>{number = 1, name = main}
download2------<NSThread: 0x600001906780>{number = 1, name = main}
download3------<NSThread: 0x600001906780>{number = 1, name = main}
需要注意的是:異步執(zhí)行雖然有開啟新線程的能力, 但是
6.同步執(zhí)行+ 主隊列
- (void)syncMainQueue{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download2------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download3------%@",[NSThread currentThread]);
});
}
執(zhí)行情況如下圖:
直接奔潰,
關(guān)于死鎖
1.概念
所謂死鎖茴迁,通常指有兩個線程T1和T2都卡住了寄悯,并等待對方完成某些操作。T1不能完成是因為它在等待T2完成堕义。但T2也不能完成猜旬,因為它在等待T1完成。于是大家都完不成,就導致了死鎖(DeadLock)
2.產(chǎn)生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用洒擦。
(2) 請求與保持條件:一個進程因請求資源而阻塞時椿争,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源熟嫩,在末使用完之前秦踪,不能強行剝奪。
(4) 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系掸茅。
這四個條件是死鎖的必要條件椅邓,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立昧狮,而只要上述條件之一不滿足景馁,就不會發(fā)生死鎖。
首先來看一份經(jīng)典的死鎖代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"這里死鎖了");
});
}
return 0;
}
首先明確的是:執(zhí)行這個dispatch_get_main_queue隊列的是主線程陵且。執(zhí)行了dispatch_sync函數(shù)后裁僧,將block添加到了main_queue中,同時調(diào)用dispatch_sync這個函數(shù)的線程(也就是主線程)被阻塞慕购,等待block執(zhí)行完成,而執(zhí)行主線程隊列任務(wù)的線程正是主線程茬底,此時他處于阻塞狀態(tài)沪悲,所以block永遠不會被執(zhí)行,因此主線程一直處于阻塞狀態(tài)阱表。因此這段代碼運行后殿如,并非卡在block中無法返回,而是根本無法執(zhí)行到這個block最爬。
同理前面死鎖的代碼,可以這樣理解:
執(zhí)行syncMainQueue這個方法是在主線程中執(zhí)行的, 你可以把它看做一個任務(wù)A, 這個任務(wù)A也是在主隊列中的,那么代碼執(zhí)行到第179行的時候, 啟動了任務(wù)B, 把任務(wù)B放進了主隊列中, 由于是同步執(zhí)行, 所以, 必須等待任務(wù)B執(zhí)行完了之后才能繼續(xù)向下執(zhí)行, 但是主線程有任務(wù)A, 所以任務(wù)B無法放到主線程中去執(zhí)行,任務(wù)B等待任務(wù)A執(zhí)行, 任務(wù)A等待任務(wù)B執(zhí)行, 這樣就造成了死鎖.
如圖
但是,如果將同步執(zhí)行+ 主隊列的操作放到子線程中執(zhí)行, 就不會造成死鎖
- (void)syncMainOnBackgroundThread{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self syncMainQueue];
});
}
執(zhí)行情況:
download1------<NSThread: 0x600001c0df40>{number = 1, name = main}
download2------<NSThread: 0x600001c0df40>{number = 1, name = main}
download3------<NSThread: 0x600001c0df40>{number = 1, name = main}
那為什么同步執(zhí)行 + 串行隊列不會造成死鎖呢?
- (void)syncSerial{
/* 1.創(chuàng)建一個串行隊列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
/* 2.將任務(wù)放在隊列中 */
dispatch_sync(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
download1--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download2--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download3--------<NSThread: 0x600003536cc0>{number = 1, name = main}
原因是:download.zavier.com這個隊列的執(zhí)行線程是主線程,同步執(zhí)行不會開辟新線程,但這時候我們將任務(wù)添加到了download.zavier.com這個隊列中,所以主線程會來立即執(zhí)行這個隊列中的任務(wù)涉馁,執(zhí)行完成后就會返回,主線程不會繼續(xù)被阻塞爱致。所以不會死鎖
事實上,導致死鎖的原因一定是:
在某一個串行隊列中烤送,同步的向這個串行隊列添加block。
說了這么多, 可能又有點暈, 其實這些應(yīng)該在實際開發(fā)中慢慢體會, 碰到的情況多了, 自然而然就明白了. 現(xiàn)在, 我們只要記住下面的表格就可以了
各種隊列的執(zhí)行效果圖:
參考文獻:
iOS中GCD產(chǎn)生死鎖原因分析及解決方案
同步,異步,串行隊列,并發(fā)隊列,全局隊列,主隊列等概念的總結(jié)