本文是GCD多線程編程基礎(chǔ)內(nèi)容的小結(jié),通過本文蘸朋,你可以了解到:
- 多線程的幾個基本概念:進(jìn)程與線程核无、串行與并發(fā)
- GCD中的2個核心內(nèi)容:隊列、任務(wù)
- GCD的基本使用步驟
- GCD中使用同步異步方式添加任務(wù)到串行并發(fā)隊列后執(zhí)行的實(shí)際效果
- GCD中產(chǎn)生死鎖的原因以及實(shí)際開發(fā)中如何避免死鎖crash
GCD
Apple為了讓開發(fā)者更加容易的使用設(shè)備上的多核CPU藕坯,蘋果在 OS X 10.6 和 iOS 4 中引入了 Grand Central Dispatch(GCD),它是 Apple 開發(fā)的一個多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器,它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù),是我們平常開發(fā)中最常見的一種多線程編程方式
多線程基本概念
進(jìn)程與線程
對于操作系統(tǒng)來說团南,一個任務(wù)就是一個進(jìn)程(Process),比如打開一個瀏覽器就是啟動一個瀏覽器進(jìn)程堕担,打開一個記事本就啟動了一個記事本進(jìn)程已慢,打開兩個記事本就啟動了兩個記事本進(jìn)程曲聂,打開一個Word就啟動了一個Word進(jìn)程霹购。
有些進(jìn)程還不止同時干一件事,比如Word朋腋,它可以同時進(jìn)行打字齐疙、拼寫檢查、打印等事情旭咽。在一個進(jìn)程內(nèi)部贞奋,要同時干多件事,就需要同時運(yùn)行多個“子任務(wù)”穷绵,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)轿塔。
由于每個進(jìn)程至少要干一件事,所以仲墨,一個進(jìn)程至少有一個線程勾缭。當(dāng)然,像Word這種復(fù)雜的進(jìn)程可以有多個線程目养,多個線程可以同時執(zhí)行俩由,多線程的執(zhí)行方式和多進(jìn)程是一樣的,也是由操作系統(tǒng)在多個線程之間快速切換癌蚁,讓每個線程都短暫地交替運(yùn)行幻梯,看起來就像同時執(zhí)行一樣。當(dāng)然努释,真正地同時執(zhí)行多線程需要多核CPU才可能實(shí)現(xiàn)碘梢。
串行和并發(fā)
并發(fā)就是多個任務(wù)在執(zhí)行的過程中,時間互相重疊伐蒂,一個任務(wù)執(zhí)行沒結(jié)束煞躬,另一個已經(jīng)開始。
串行就是任務(wù)一個一個的執(zhí)行饿自,時間上不相互重疊汰翠,一個任務(wù)執(zhí)行結(jié)束龄坪,下一個任務(wù)才能開始執(zhí)行。
GCG隊列
隊列是一種特殊的線性表复唤,特殊之處在于它只允許在表的后端進(jìn)入插入操作健田,在表的前端進(jìn)行刪除操作,即遵循FIFO
原則佛纫。
GCD中的隊列(Dispatch Queue
)就是指用來執(zhí)行任務(wù)的等待隊列妓局,當(dāng)我們添加任務(wù)到隊列之后,開發(fā)者不用再直接跟線程
打交道了呈宇,只需要向隊列中添加代碼塊即可好爬,GCD 在后端管理著一個線程池。GCD 不僅決定著你的代碼塊將在哪個線程被執(zhí)行甥啄,它還根據(jù)可用的系統(tǒng)資源對這些線程進(jìn)行管理存炮。這樣可以將開發(fā)者從線程管理的工作中解放出來,通過集中的管理線程蜈漓,來緩解大量線程被創(chuàng)建的問題穆桂。
GCD中的隊列可以分為以下2種:
-
串行隊列 ( Serial Dispatch Queue )
串行隊列(也稱為私有調(diào)度隊列)按照將他們添加到隊列順序一次執(zhí)行一個任務(wù)。當(dāng)前正在執(zhí)行的任務(wù)在由隊列管理的不同線程(可能因任務(wù)而異)上運(yùn)行融虽。串行隊列通常用于同步對特定資源的訪問享完。
-
并發(fā)隊列 ( Concurrent Dispatch Queue )
并發(fā)隊列(也稱為一種全局調(diào)度隊列)同時執(zhí)行一個或多個任務(wù),但任務(wù)仍按其添加到隊列的順序啟動有额。當(dāng)前正在執(zhí)行的任務(wù)在由調(diào)度隊列管理的不同線程上運(yùn)行般又。在任何給定點(diǎn)執(zhí)行的任務(wù)的確切數(shù)量是可變的,取決于系統(tǒng)條件巍佑。
在我們平時的開發(fā)中向楼,還有2種我們最常見的加勤,也是使用頻率最高的隊列:
-
主隊列 ( Main Dispatch Queue )
主隊列是一個全局可用的串行隊列,它在應(yīng)用程序的主線程上執(zhí)行任務(wù)。此隊列與應(yīng)用程序的
Runloop
一起工作它浅,將有序任務(wù)的執(zhí)行與附加到Runloop
的其他事件源的執(zhí)行交錯计济。因?yàn)樗趹?yīng)用程序的主線程上運(yùn)行作岖,所以主隊列通常用作應(yīng)用程序的關(guān)鍵同步點(diǎn)惦积。主隊列下的任務(wù)不管是異步任務(wù)還是同步任務(wù)都不會開辟線程,任務(wù)只會在主線程順序執(zhí)行
-
全局并發(fā)隊列 ( Global Dispatch Queue )
全局并發(fā)隊列本質(zhì)上是一個并發(fā)隊列筹吐,有系統(tǒng)提供糖耸,方便編程,可以不用創(chuàng)建就可以直接使用
GCD任務(wù)
任務(wù)就是你要在線程中執(zhí)行的代碼丘薛,在GCD中是用Block來定義任務(wù)的嘉竟,是用起來非常靈活便捷。
GCD中執(zhí)行任務(wù)的方式有兩種:同步執(zhí)行(sync)與異步執(zhí)行(async)
-
同步執(zhí)行
同步執(zhí)行就是指使用
dispatch_sync
方法將任務(wù)同步的添加到隊列里,在添加的任務(wù)執(zhí)行結(jié)束之前舍扰,當(dāng)前線程會被阻塞倦蚪,然后會一直等待,直到任務(wù)完成边苹。dispatch_sync
添加的任務(wù)只能在當(dāng)前線程執(zhí)行陵且,不具備開啟新線程的能力 -
異步執(zhí)行
異步執(zhí)行就是指使用
dispatch_async
方法將任務(wù)異步的添加到隊列里,它不需要等待任務(wù)執(zhí)行結(jié)束个束,不需要做任何等待就能繼續(xù)執(zhí)行任務(wù)dispatch_async
添加的任務(wù)可以在新的線程中執(zhí)行任務(wù)慕购,具備開啟新線程的能力,但并不一定會開啟新線程
GCD的使用步驟
這個就跟趙本山跟宋丹丹的小品《鐘點(diǎn)工》里提出的把大象裝進(jìn)冰箱的經(jīng)典問題一樣茬底,都是分三步:
把大象裝進(jìn)冰箱
- 把冰箱門打開
- 把大象裝進(jìn)去
- 把冰箱門關(guān)上
GCD使用步驟
- 創(chuàng)建或獲取一個隊列
- 定制需要執(zhí)行的任務(wù)
- 將任務(wù)追加到隊列
創(chuàng)建或獲取一個隊列
使用
dispatch_get_main_queue()
獲取主隊列沪悲。-
使用
dispatch_get_global_queue
獲取全局并發(fā)隊列,這個函數(shù)有2個參數(shù)阱表,第一個參數(shù)是全局隊列的優(yōu)先級殿如,一般情況下,使用的都是DISPATCH_QUEUE_PRIORITY_DEFAULT
優(yōu)先級捶枢,第二個參數(shù)是一個保留字段握截,我們需要給它一個0
,否則這個函數(shù)會返回一個NULL
,導(dǎo)致我們獲取不到正常的全局隊列。Reserved for future use. Passing any value other than zero may result in a NULL return value.
-
使用
dispatch_queue_create
函數(shù)烂叔,創(chuàng)建自定義的串行或并行隊列,這個函數(shù)的定義如下:* @param label * A string label to attach to the queue. * This parameter is optional and may be NULL. * * @param attr * A predefined attribute such as DISPATCH_QUEUE_SERIAL, * DISPATCH_QUEUE_CONCURRENT, or the result of a call to * a dispatch_queue_attr_make_with_* function. * * @result * The newly created dispatch queue. */ API_AVAILABLE(macos(10.6), ios(4.0)) DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT DISPATCH_NOTHROW dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
dispatch_queue_create
函數(shù)最后返回了一個隊列dispatch_queue_t
,這個函數(shù)有2個參數(shù)固歪,第一個參數(shù)其實(shí)可以看成是是我們給這個隊列取的名字蒜鸡,以便后續(xù)debug
,蘋果官方是推薦開發(fā)者使用逆序全程域名。第二個參數(shù)牢裳,是用于確定這個隊列是串行隊列還是并發(fā)隊列逢防,使用
DISPATCH_QUEUE_CONCURRENT
表示創(chuàng)建的隊列是并發(fā)隊列,使用DISPATCH_QUEUE_SERIAL
或者NULL
表示創(chuàng)建的隊列是串行隊列蒲讯,它們兩個其實(shí)是等價的忘朝,見下面的注釋:/*! * @const DISPATCH_QUEUE_SERIAL * * @discussion * An attribute that can be used to create a dispatch queue that invokes blocks * serially in FIFO order. * * See dispatch_queue_serial_t. */ #define DISPATCH_QUEUE_SERIAL NULL
不過個人不推薦使用
NULL
的方式來表示創(chuàng)建的是串行隊列,這種方式在多人開發(fā)時判帮,閱讀性是比較差的局嘁。以下是獲取或創(chuàng)建的4種方式:
//獲取自定義串行隊列 self.serialQueue = dispatch_queue_create("com.zed.customSerialQueue", DISPATCH_QUEUE_SERIAL); //獲取自定義并發(fā)隊列 self.concurrentQueue = dispatch_queue_create("com.zed.customConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); //獲取主隊列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); //獲取全局并發(fā)隊列 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
定制需要執(zhí)行的任務(wù)
GCD種的任務(wù)其實(shí)就是一個Block
,就是我們俗稱的代碼塊晦墙,在這個代碼塊里面悦昵,把我們需要做的事情就是,將我們的任務(wù)代碼加入到這個block中
void (^block)(void) = ^{
NSLog(@"執(zhí)行任務(wù)");
for (int i = 0; i<100; i++) {
NSLog(@"%d",i);
}
NSLog(@"Thread:%@",[NSThread currentThread]);
};
將任務(wù)追加到隊列
GCD提供了2個方法用于將任務(wù)追加到隊列:
-
dispatch_sync
使用同步執(zhí)行的方式追加到隊列 -
dispatch_async
使用異步的方式追加到隊列
//三晌畅、將任務(wù)增加到隊列中
dispatch_async(globalQueue, block);
GCD的基本使用
前面我們已經(jīng)介紹了兩種基本隊列(串行隊列與并發(fā)隊列)但指,兩種特殊隊列(主隊列與全局并發(fā)隊列),兩種任務(wù)執(zhí)行方式(同步執(zhí)行與異步執(zhí)行),所以棋凳,我們就有了8中不同的組合方式拦坠,不過由于全局并發(fā)隊列跟普通并發(fā)隊列的性質(zhì)是差不多的,所以剩岳,我們就有6中不同的組合贪婉,接下來,我們從3個角度來觀察這6種組合方式的效果:
三個角度
- 是否開啟線程
- 任務(wù)是按序執(zhí)行還是交替(同時)執(zhí)行
- 是否阻塞當(dāng)前線程
六種組合方式
- 同步執(zhí)行+并發(fā)隊列
- 異步執(zhí)行+并發(fā)隊列
- 同步執(zhí)行+串行隊列
- 異步執(zhí)行+串行隊列
- 同步執(zhí)行+主隊列
- 異步執(zhí)行+主隊列
同步執(zhí)行+并發(fā)隊列
#pragma mark - 同步執(zhí)行+并發(fā)隊列
/*
* 特點(diǎn):
* 1.在當(dāng)前線程中執(zhí)行任務(wù)卢肃,不會開啟新線程
* 2.按序執(zhí)行任務(wù)疲迂,執(zhí)行行完一個任務(wù),再執(zhí)行下一個任務(wù)
* 3.會阻塞當(dāng)前線程
*/
- (IBAction)executeSyncConcurrencyTask:(UIButton *)sender {
NSLog(@"CurrentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncConcurrencyTask---begin");
dispatch_sync(self.concurrentQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(self.concurrentQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(self.concurrentQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"SyncConcurrencyTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 13:42:07.265811+0800 GCD(一) 隊列莫湘、任務(wù)尤蒿、串行、并發(fā)[8668:1868026] CurrentThread---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:07.265945+0800 GCD(一) 隊列幅垮、任務(wù)腰池、串行、并發(fā)[8668:1868026] SyncConcurrencyTask---begin
2019-04-22 13:42:09.266681+0800 GCD(一) 隊列忙芒、任務(wù)示弓、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:11.268049+0800 GCD(一) 隊列呵萨、任務(wù)奏属、串行、并發(fā)[8668:1868026] 1---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:12.299360+0800 GCD(一) 隊列潮峦、任務(wù)囱皿、串行、并發(fā)[8668:1868133] XPC connection interrupted
2019-04-22 13:42:13.269544+0800 GCD(一) 隊列忱嘹、任務(wù)嘱腥、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:15.270540+0800 GCD(一) 隊列拘悦、任務(wù)齿兔、串行、并發(fā)[8668:1868026] 2---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:17.271936+0800 GCD(一) 隊列础米、任務(wù)分苇、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273478+0800 GCD(一) 隊列椭盏、任務(wù)组砚、串行、并發(fā)[8668:1868026] 3---<NSThread: 0x600002969400>{number = 1, name = main}
2019-04-22 13:42:19.273713+0800 GCD(一) 隊列掏颊、任務(wù)糟红、串行艾帐、并發(fā)[8668:1868026] SyncConcurrencyTask---end
2019-04-22 13:42:19.273857+0800 GCD(一) 隊列、任務(wù)盆偿、串行柒爸、并發(fā)[8668:1868026] *********************************************************
通過我們的代碼測驗(yàn),可以看出:
所有的任務(wù)都是在主線程(當(dāng)前線程)中執(zhí)行的事扭,并沒有開啟新的線程捎稚,這也說明了同步執(zhí)行的一個特性:同步執(zhí)行任務(wù)不具備開啟新線程的能力。
任務(wù)1求橄、任務(wù)2今野、任務(wù)3是按順序執(zhí)行的,并沒有出現(xiàn)并發(fā)執(zhí)行的情況罐农,這是因?yàn)殡m然并發(fā)隊列具備同時執(zhí)行多個任務(wù)的能力条霜,但是由于是同步執(zhí)行不具備開啟新線程的能力,所以涵亏,即使任務(wù)被追加到了并發(fā)隊列宰睡,它也沒有辦法去開啟新的線程,只能在當(dāng)前線程中執(zhí)行任務(wù)气筋。
從我們的log中可以看出拆内,我們所有的任務(wù)都是在
begin
與end
之間的,所以說宠默,它會阻塞當(dāng)前線程麸恍,等待隊列中的任務(wù)執(zhí)行結(jié)束,才會繼續(xù)執(zhí)行下面的代碼光稼。
異步執(zhí)行+并發(fā)隊列
#pragma mark - 異步執(zhí)行+并發(fā)隊列
/*
* 特點(diǎn):
* 1.開啟多個新線程執(zhí)行任務(wù)
* 2.任務(wù)交替(同時)執(zhí)行
* 3.不會阻塞當(dāng)前線程
*/
- (IBAction)executeAsyncConcurrencyTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncConcurrencyTask---begin");
dispatch_async(self.concurrentQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(self.concurrentQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(self.concurrentQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncConcurrencyTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 14:32:14.941222+0800 GCD(一) 隊列或南、任務(wù)、串行艾君、并發(fā)[9417:2009244] CurrentThread begin---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941405+0800 GCD(一) 隊列、任務(wù)肄方、串行冰垄、并發(fā)[9417:2009244] AsyncConcurrencyTask---begin
2019-04-22 14:32:14.941608+0800 GCD(一) 隊列、任務(wù)权她、串行虹茶、并發(fā)[9417:2009244] CurrentThread end---<NSThread: 0x6000000cea40>{number = 1, name = main}
2019-04-22 14:32:14.941757+0800 GCD(一) 隊列、任務(wù)隅要、串行蝴罪、并發(fā)[9417:2009244] AsyncConcurrencyTask---end
2019-04-22 14:32:14.941894+0800 GCD(一) 隊列、任務(wù)步清、串行要门、并發(fā)[9417:2009244] *********************************************************
2019-04-22 14:32:16.945860+0800 GCD(一) 隊列虏肾、任務(wù)、串行欢搜、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊列封豪、任務(wù)、串行炒瘟、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:16.945909+0800 GCD(一) 隊列吹埠、任務(wù)、串行疮装、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊列缘琅、任務(wù)、串行廓推、并發(fā)[9417:2011460] 2---<NSThread: 0x6000000aec00>{number = 5, name = (null)}
2019-04-22 14:32:18.951121+0800 GCD(一) 隊列刷袍、任務(wù)、串行受啥、并發(fā)[9417:2011461] 3---<NSThread: 0x6000000a2340>{number = 6, name = (null)}
2019-04-22 14:32:18.951120+0800 GCD(一) 隊列做个、任務(wù)、串行滚局、并發(fā)[9417:2009294] 1---<NSThread: 0x6000000a2380>{number = 4, name = (null)}
通過我們的代碼測驗(yàn)居暖,可以看出:
- 除了在主線程執(zhí)行的2個log任務(wù)之外,系統(tǒng)又開啟了3個線程用于執(zhí)行追加的三個任務(wù)藤肢,說明異步執(zhí)行具備開啟新線程的能力太闺,并且并發(fā)隊列可以開啟多個線程,交替執(zhí)行多個任務(wù)嘁圈。
- 從我們的log中可以看到省骂,
begin
的log之后,馬上就是end
的log,因此可以看出最住,它并不會阻塞當(dāng)前線程钞澳,并不需要等待追加的任務(wù)執(zhí)行完成。
同步執(zhí)行+串行隊列
#pragma mark - 同步執(zhí)行+串行隊列
/*
* 特點(diǎn):
* 1.在當(dāng)前線程中執(zhí)行任務(wù)涨缚,不會開啟新線程
* 2.按序執(zhí)行任務(wù)轧粟,執(zhí)行行完一個任務(wù),再執(zhí)行下一個任務(wù)
* 3.會阻塞當(dāng)前線程
*/
- (IBAction)executeSyncSerialTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncSerialTask---begin");
dispatch_sync(self.serialQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(self.serialQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(self.serialQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncSerialTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 15:02:52.760352+0800 GCD(一) 隊列脓魏、任務(wù)兰吟、串行、并發(fā)[9826:2087150] CurrentThread begin---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:52.760558+0800 GCD(一) 隊列茂翔、任務(wù)混蔼、串行、并發(fā)[9826:2087150] SyncSerialTask---begin
2019-04-22 15:02:54.761971+0800 GCD(一) 隊列珊燎、任務(wù)惭嚣、串行遵湖、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:56.762653+0800 GCD(一) 隊列、任務(wù)料按、串行奄侠、并發(fā)[9826:2087150] 1---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:02:58.764202+0800 GCD(一) 隊列、任務(wù)载矿、串行垄潮、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:00.765234+0800 GCD(一) 隊列、任務(wù)闷盔、串行弯洗、并發(fā)[9826:2087150] 2---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:02.766464+0800 GCD(一) 隊列、任務(wù)逢勾、串行牡整、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.767966+0800 GCD(一) 隊列、任務(wù)溺拱、串行逃贝、并發(fā)[9826:2087150] 3---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768230+0800 GCD(一) 隊列、任務(wù)迫摔、串行沐扳、并發(fā)[9826:2087150] CurrentThread end---<NSThread: 0x600000222800>{number = 1, name = main}
2019-04-22 15:03:04.768379+0800 GCD(一) 隊列、任務(wù)句占、串行沪摄、并發(fā)[9826:2087150] SyncSerialTask---end
2019-04-22 15:03:04.768516+0800 GCD(一) 隊列、任務(wù)纱烘、串行杨拐、并發(fā)[9826:2087150] *********************************************************
通過我們的代碼測驗(yàn),可以看出:
- 所有的任務(wù)都是在主線程(當(dāng)前線程)中執(zhí)行的擂啥,并且是順序執(zhí)行的哄陶,沒有開啟新的線程。
- 從我們的log中可以看出哺壶,我們所有的任務(wù)都是在
begin
與end
之間的奕筐,所以說,它會阻塞當(dāng)前線程变骡,等待隊列中的任務(wù)執(zhí)行結(jié)束,才會繼續(xù)執(zhí)行下面的代碼芭逝。
異步執(zhí)行+串行隊列
#pragma mark - 異步執(zhí)行+串行隊列
/*
* 特點(diǎn):
* 1.會開啟一條新線程
* 2.按序執(zhí)行任務(wù)塌碌,執(zhí)行行完一個任務(wù),再執(zhí)行下一個任務(wù)
* 3.不會阻塞當(dāng)前線程
*/
- (IBAction)executeAsyncSerialTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncSerialTask---begin");
dispatch_async(self.serialQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(self.serialQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(self.serialQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncSerialTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 15:25:00.103488+0800 GCD(一) 隊列旬盯、任務(wù)台妆、串行翎猛、并發(fā)[10181:2154024] CurrentThread begin---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103734+0800 GCD(一) 隊列、任務(wù)接剩、串行切厘、并發(fā)[10181:2154024] AsyncSerialTask---begin
2019-04-22 15:25:00.103888+0800 GCD(一) 隊列、任務(wù)懊缺、串行疫稿、并發(fā)[10181:2154024] CurrentThread end---<NSThread: 0x6000019c9400>{number = 1, name = main}
2019-04-22 15:25:00.103986+0800 GCD(一) 隊列、任務(wù)鹃两、串行遗座、并發(fā)[10181:2154024] AsyncSerialTask---end
2019-04-22 15:25:00.104091+0800 GCD(一) 隊列、任務(wù)俊扳、串行途蒋、并發(fā)[10181:2154024] *********************************************************
2019-04-22 15:25:02.108899+0800 GCD(一) 隊列、任務(wù)馋记、串行号坡、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:04.111910+0800 GCD(一) 隊列、任務(wù)梯醒、串行宽堆、并發(fā)[10181:2154074] 1---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:06.116733+0800 GCD(一) 隊列、任務(wù)冤馏、串行日麸、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:08.117706+0800 GCD(一) 隊列代箭、任務(wù)、串行嗡综、并發(fā)[10181:2154074] 2---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:10.122737+0800 GCD(一) 隊列杜漠、任務(wù)、串行驾茴、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
2019-04-22 15:25:12.126742+0800 GCD(一) 隊列盼樟、任務(wù)、串行锈至、并發(fā)[10181:2154074] 3---<NSThread: 0x600001992fc0>{number = 4, name = (null)}
通過我們的代碼測驗(yàn)晨缴,可以看出:
- 三個追加的任務(wù)都是在一個新的線程中執(zhí)行的,在串行隊列中異步執(zhí)行任務(wù)峡捡,會開啟一條新線程击碗,由于隊列是串行的筑悴,所以任務(wù)是按序執(zhí)行的。
- 從我們的log中可以看到稍途,
begin
的log之后阁吝,馬上就是end
的log,因此可以看出,它并不會阻塞當(dāng)前線程械拍,并不需要等待追加的任務(wù)執(zhí)行完成突勇。
異步執(zhí)行+主隊列
#pragma mark - 異步執(zhí)行+主隊列
/*
* 特點(diǎn):
* 1.在當(dāng)前線程(主線程)中執(zhí)行任務(wù)
* 2.按序執(zhí)行任務(wù),執(zhí)行行完一個任務(wù)殊者,再執(zhí)行下一個任務(wù)
* 3.不會阻塞當(dāng)前線程
*/
- (IBAction)executeAsyncMainQueueTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncMainQueueTask---begin");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(mainQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_async(mainQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"AsyncMainQueueTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 18:28:03.990381+0800 GCD(一) 隊列与境、任務(wù)、串行猖吴、并發(fā)[12894:2615623] CurrentThread begin---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990589+0800 GCD(一) 隊列摔刁、任務(wù)、串行海蔽、并發(fā)[12894:2615623] AsyncMainQueueTask---begin
2019-04-22 18:28:03.990808+0800 GCD(一) 隊列共屈、任務(wù)、串行拗引、并發(fā)[12894:2615623] CurrentThread end---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:03.990959+0800 GCD(一) 隊列矾削、任務(wù)哼凯、串行断部、并發(fā)[12894:2615623] AsyncMainQueueTask---end
2019-04-22 18:28:03.991088+0800 GCD(一) 隊列蝴光、任務(wù)、串行沉唠、并發(fā)[12894:2615623] *********************************************************
2019-04-22 18:28:05.993620+0800 GCD(一) 隊列、任務(wù)纱扭、串行暗赶、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:07.994036+0800 GCD(一) 隊列蹂随、任務(wù)、串行激率、并發(fā)[12894:2615623] 1---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:09.995547+0800 GCD(一) 隊列乒躺、任務(wù)、串行讳推、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:11.997136+0800 GCD(一) 隊列娜遵、任務(wù)、串行纳胧、并發(fā)[12894:2615623] 2---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:13.997717+0800 GCD(一) 隊列跑慕、任務(wù)牢硅、串行、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}
2019-04-22 18:28:15.998158+0800 GCD(一) 隊列位岔、任務(wù)抒抬、串行擦剑、并發(fā)[12894:2615623] 3---<NSThread: 0x600003ee6900>{number = 1, name = main}
通過我們的代碼測驗(yàn),可以看出:
3個任務(wù)在主線程中捉撮,按序執(zhí)行
從我們的log中可以看到巾遭,
begin
的log之后,馬上就是end
的log,因此可以看出骑素,它并不會阻塞當(dāng)前線程献丑,并不需要等待追加的任務(wù)執(zhí)行完成。
同步執(zhí)行+主隊列
#pragma mark - 同步執(zhí)行+主隊列
/*
* 特點(diǎn):
* 會直接產(chǎn)生死鎖
*/
- (IBAction)executeSyncMainQueueTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncMainQueueTask---begin");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(mainQueue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
dispatch_sync(mainQueue, ^{
// 追加任務(wù)3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncMainQueueTask---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 16:04:57.238644+0800 GCD(一) 隊列邦邦、任務(wù)、串行、并發(fā)[10673:2238846] CurrentThread begin---<NSThread: 0x6000038ca800>{number = 1, name = main}
2019-04-22 16:04:57.238915+0800 GCD(一) 隊列、任務(wù)罗捎、串行、并發(fā)[10673:2238846] SyncMainQueueTask---begin
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x000000010d45fa19 libdispatch.dylib`__DISPATCH_WAIT_FOR_QUEUE__ + 444
frame #1: 0x00007ffee542deb0
frame #2: 0x000000010d45f3f0 libdispatch.dylib`_dispatch_sync_f_slow + 231
* frame #3: 0x000000010a7cff5a GCD(一) 隊列倒得、任務(wù)、串行菩彬、并發(fā)`-[ViewController executeSyncMainQueueTask:](self=0x00007fb8f25124f0, _cmd="executeSyncMainQueueTask:", sender=0x00007fb8f2515fa0) at ViewController.m:220
frame #4: 0x000000010e9b9ecb UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
frame #5: 0x000000010e3f50bd UIKitCore`-[UIControl sendAction:to:forEvent:] + 67
frame #6: 0x000000010e3f53da UIKitCore`-[UIControl _sendActionsForEvents:withEvent:] + 450
frame #7: 0x000000010e3f431e UIKitCore`-[UIControl touchesEnded:withEvent:] + 583
frame #8: 0x000000010e9f50a4 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 2729
frame #9: 0x000000010e9f67a0 UIKitCore`-[UIWindow sendEvent:] + 4080
frame #10: 0x000000010e9d4394 UIKitCore`-[UIApplication sendEvent:] + 352
frame #11: 0x000000010eaa95a9 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 3054
frame #12: 0x000000010eaac1cb UIKitCore`__handleEventQueueInternal + 5948
frame #13: 0x000000010bab8721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #14: 0x000000010bab7f93 CoreFoundation`__CFRunLoopDoSources0 + 243
frame #15: 0x000000010bab263f CoreFoundation`__CFRunLoopRun + 1263
frame #16: 0x000000010bab1e11 CoreFoundation`CFRunLoopRunSpecific + 625
frame #17: 0x00000001141491dd GraphicsServices`GSEventRunModal + 62
frame #18: 0x000000010e9b881d UIKitCore`UIApplicationMain + 140
frame #19: 0x000000010a7d03c0 GCD(一) 隊列秉馏、任務(wù)萝究、串行琴昆、并發(fā)`main(argc=1, argv=0x00007ffee542ff90) at main.m:14
frame #20: 0x000000010d4c7575 libdyld.dylib`start + 1
(lldb)
通過我們的代碼測驗(yàn)业舍,可以看出:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) frame #0: 0x000000010d45fa19 libdispatch.dylib`__DISPATCH_WAIT_FOR_QUEUE__ + 444
應(yīng)用在主線程同步執(zhí)行第一個任務(wù)時,就會直接crash,我們同步
LLDB
的bt
指定查看函數(shù)調(diào)用棧,可以發(fā)現(xiàn)舷暮,在系統(tǒng)庫libdispatch
調(diào)用__DISPATCH_WAIT_FOR_QUEUE__
函數(shù)時态罪,就會產(chǎn)生一個由隊列引起的循環(huán)等待導(dǎo)致的crash,這就是我們常說的Deadlock
死鎖,接下里我們來詳細(xì)介紹一下死鎖產(chǎn)生的原因與注意事項(xiàng)下面。
GCD中產(chǎn)生死鎖的原因
- (IBAction)executeSyncMainQueueTask:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"SyncMainQueueTask---begin");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
});
從上面的代碼我們可以看出复颈,當(dāng)我們點(diǎn)擊按鈕,調(diào)用executeSyncMainQueueTask
方法時沥割,這時我們其實(shí)是在主隊列(串行隊列)提交了一個任務(wù),我們暫先稱它為任務(wù)0
然后我們又使用dispatch_sync
同步執(zhí)行方法往主隊列中提交了任務(wù)1
的Block
,現(xiàn)在我們來分析一下蚀苛,為什么這種情況下會產(chǎn)生死鎖
- 先往主隊列(串行隊列)中提交了任務(wù)0侦厚,然后在任務(wù)0執(zhí)行的過程中同步地往主隊列中添加了任務(wù)1
- 主隊列中添加的任務(wù)都會在主線程中執(zhí)行岛心,同時按照串行隊列的特點(diǎn)(任務(wù)按序執(zhí)行)送朱,主線程中首先會執(zhí)行任務(wù)0大年,任務(wù)0執(zhí)行完成之后才會去執(zhí)行任務(wù)1,但是在任務(wù)0執(zhí)行的過程中,使用同步方式往主隊列中添加任務(wù)1碑定,由于是使用同步方式,這時主線程會被阻塞审编,需要任務(wù)1完成之后,任務(wù)0才會繼續(xù)往下執(zhí)行券时。由此适肠,我們可以看出辩稽,由于串行隊列的特性并级,任務(wù)1會依賴于任務(wù)0的執(zhí)行完成才會繼續(xù)往下執(zhí)行嘲碧,同時由于同步添加任務(wù)的特性(會阻塞當(dāng)前線程履婉,直到添加的任務(wù)執(zhí)行完成)胯究,任務(wù)0會依賴于任務(wù)1的執(zhí)行完成督暂。所以,2個任務(wù)的執(zhí)行就會因?yàn)橄嗷サ却龑Ψ降耐瓿烧鴮?dǎo)致死鎖管引。
通過上面的分析,我們可以看出這里產(chǎn)生死鎖的一個很重要的原因就是主隊列是一個串行的隊列(主隊列中只有一條主線程)闯两。如果我們?nèi)缦吕彀椋诓l(fā)隊列中提交谅将,則不會造成死鎖:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //這里是為了保證當(dāng)前任務(wù)是處于并發(fā)隊列開辟的線程中,而不是主線程中
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)0");
});
NSLog(@"任務(wù)1");
});
原因是并發(fā)隊列中的任務(wù)執(zhí)行時并行的重慢,所以饥臂,任務(wù)1并不會一直等待任務(wù)0執(zhí)行完成,才去執(zhí)行似踱,而是直接執(zhí)行完隅熙。因此任務(wù)0因?yàn)槿蝿?wù)1的結(jié)束,線程阻塞也會被消除屯援,任務(wù)0得以繼續(xù)執(zhí)行猛们。
我們再開看一組示例:
//目前處于主線程中
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)0");
});
NSLog(@"任務(wù)1");
我們在主線程中,往全局隊列同步提交了Block狞洋,因?yàn)槿株犃泻椭麝犃惺莾蓚€隊列弯淘,所以任務(wù)1的執(zhí)行,并不需要等待任務(wù)0吉懊。所以等任務(wù)0結(jié)束庐橙,任務(wù)1也可以被執(zhí)行。
當(dāng)然這里因?yàn)樘峤籅lock所在隊列借嗽,Block被執(zhí)行的隊列是完全不同的兩個隊列态鳖,所以這里用串行queue,也是不會死鎖的恶导,到這里我們也可以知道一些同步提交(dispatch_sync
)的阻塞機(jī)制:
同步提交Block,首先是阻塞的當(dāng)前提交Block的線程浆竭,而在隊列中,同步提交的Block,只會阻塞串行隊列(由串行隊列的同一時間只能執(zhí)行一個任務(wù)的特性決定)惨寿,并不會阻塞并發(fā)隊列邦泄,當(dāng)然dispatch_barrier
系列的除外,這個我會在后面的文章中講到裂垦,歡迎大家繼續(xù)關(guān)注我的博客
現(xiàn)在我們可以用一句話來總結(jié)產(chǎn)生死鎖的原因就是:
使用同步方式(
dispatch_sync
)提交一個任務(wù)到一個串行隊列時顺囊,如果提交這個任務(wù)的操作所處的線程,也是處于這個串行隊列蕉拢,就會引起死鎖
開發(fā)中如何避免產(chǎn)生死鎖
- 不要在主線程中使用同步方式添加任務(wù)到主隊列
- 不要嵌套使用在自定義的串行隊列中特碳,嵌套使用同步方式添加任務(wù)到該串行隊列
6種組合使用總結(jié)
總結(jié) | 串行隊列 | 并發(fā)隊列 | 主隊列 |
---|---|---|---|
同步添加(sync) | 不開辟新線程,在當(dāng)前線程中串行執(zhí)行任務(wù) | 不開辟新線程晕换,在當(dāng)前線程中串行執(zhí)行任務(wù) | 死鎖 |
異步添加(async) | 開辟新線程(1條)午乓,串行執(zhí)行任務(wù) | 開辟新線程(1/n條),并發(fā)執(zhí)行任務(wù) | 不開辟新線程闸准,在主線程中順序執(zhí)行 |
線程間通信
#pragma mark - 子線程執(zhí)行耗時代碼硅瞧,主線程更新UI
- (IBAction)threadInteraction:(UIButton *)sender {
NSLog(@"CurrentThread begin---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"threadInteraction---begin");
//異步添加任務(wù)到全局并發(fā)隊列執(zhí)行耗時操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//執(zhí)行耗時任務(wù)
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
//回到主線程更新UI
dispatch_sync(dispatch_get_main_queue(), ^{
//Do something here to update UI
});
});
NSLog(@"CurrentThread end---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"threadInteraction---end");
NSLog(@"*********************************************************");
}
執(zhí)行結(jié)果如下:
2019-04-22 18:47:54.836027+0800 GCD(一) 隊列、任務(wù)恕汇、串行腕唧、并發(fā)[13229:2669381] CurrentThread begin---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836217+0800 GCD(一) 隊列或辖、任務(wù)、串行枣接、并發(fā)[13229:2669381] threadInteraction---begin
2019-04-22 18:47:54.836436+0800 GCD(一) 隊列颂暇、任務(wù)、串行但惶、并發(fā)[13229:2669381] CurrentThread end---<NSThread: 0x6000038e9400>{number = 1, name = main}
2019-04-22 18:47:54.836619+0800 GCD(一) 隊列耳鸯、任務(wù)、串行膀曾、并發(fā)[13229:2669381] threadInteraction---end
2019-04-22 18:47:54.836761+0800 GCD(一) 隊列县爬、任務(wù)、串行添谊、并發(fā)[13229:2669381] *********************************************************
2019-04-22 18:47:56.839416+0800 GCD(一) 隊列财喳、任務(wù)、串行斩狱、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}
2019-04-22 18:47:58.840646+0800 GCD(一) 隊列耳高、任務(wù)、串行所踊、并發(fā)[13229:2669427] 1---<NSThread: 0x6000038b6d40>{number = 4, name = (null)}
在平常的開發(fā)中泌枪,我們最常用的就是提交一個任務(wù)到全局并發(fā)隊列取執(zhí)行一些比較耗時的操作(比如,文件下載秕岛、文件上傳碌燕、圖片解碼、數(shù)據(jù)庫操作继薛、IO讀寫)陆蟆,然后再切回到主線程去更新UI,蘋果官方限定了更新UI的操作只能在主線程中執(zhí)行,所以惋增,我們最后還是要回到主線程去處理我們的UI交互。
本文到這里已經(jīng)基本結(jié)束改鲫,在接下來的文章中诈皿,我將會繼續(xù)講解GCD多線程編程的另外幾個知識點(diǎn),也是我們平時開發(fā)中實(shí)際很經(jīng)常會用到的像棘,如dispatch_barrier
稽亏、dispatch_group
、dispatch_semaphore
缕题、線程安全
的相關(guān)內(nèi)容
如果文中有錯誤的地方截歉,或者與你的想法相悖的地方,請在評論區(qū)告知我烟零,我會繼續(xù)改進(jìn)瘪松,如果你覺得這個篇文章總結(jié)的還不錯咸作,麻煩動動小手,給我的文章與Git代碼樣例點(diǎn)個?