目錄
一、GCD簡介
- 什么是GCD:
- 全稱是Grand Central Dispatch
- 純C語言锚扎,提供了非常多強大的函數(shù)
- 將任務(wù)添加到隊列吞瞪,并且指定執(zhí)行任務(wù)的函數(shù)
- GCD的優(yōu)勢:
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
- GCD會自動管理線程的生命周期(創(chuàng)建線程驾孔、調(diào)度任務(wù)芍秆、銷毀線程)
- 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
二翠勉、任務(wù)妖啥、函數(shù)、隊列
- 任務(wù)使用
block
封裝 - 任務(wù)的
block
沒有參數(shù)也沒有返回值 - 執(zhí)行任務(wù)的函數(shù)
- 異步
dispatch async
函數(shù)
不用等待當前語句執(zhí)行完畢对碌,就可以執(zhí)行下一條語句
會開啟線程執(zhí)行block
的任務(wù)
異步是多線程的代名詞
- 異步
- 同步
dispatch sync
函數(shù)
必須等待當前語句執(zhí)行完畢荆虱,才會執(zhí)行下一條語句
不會開啟線程
在當前線程執(zhí)行block
的任務(wù)
- 隊列
隊列用來調(diào)度任務(wù),任務(wù)都做線程中執(zhí)行
串行隊列和并發(fā)隊列都符合FIFO原則
(先進先出并不是先進先執(zhí)行完成)
- 隊列和函數(shù)
異步函數(shù)+并發(fā)隊列 開啟多條新的子線程、并發(fā)
異步函數(shù)+串行隊列 為所有串行隊列中的任務(wù)僅開啟一個新的子線程怀读、串行
同步函數(shù)+并發(fā)隊列 同步函數(shù)中均不開啟新的子線程诉位、串行
同步函數(shù)+串行隊列 同上...
- 主隊列和全局隊列
主隊列
- 專門用來在
主線程
上調(diào)度任務(wù)的串行隊列
- 不會開啟線程
- 如果當前主線程正在有任務(wù)執(zhí)行,那么無論主隊列中當前被添加了什么任務(wù)菜枷,都不會被調(diào)度
dispatch get main queue();
全局隊列
- 為了方便程序員的使用苍糠,蘋果提供了全局隊列dispatch_get_global queue(0, 0)
- 全局隊列是一個
并發(fā)隊列
- 在使用多線程開發(fā)時,如果對隊列沒有特殊需求啤誊,在執(zhí)行異步任務(wù)時椿息,可以直接使用全局隊列
三、GCD相關(guān)面試題
- 下面代碼輸出什么坷衍?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
這個程序就是典型的死鎖寝优,可以看到,只打印了1
枫耳,就再也沒有響應(yīng)了乏矾,已經(jīng)造成了GCD死鎖。為什么會這樣呢迁杨?讓我們來解讀一下這段程序的運行順序:首先會打印1
钻心,然后將主隊列和一個block傳入GCD同步函數(shù)dispatch_sync中,等待sync函數(shù)執(zhí)行铅协,直到它返回捷沸,才會執(zhí)行打印3
的語句『罚可是痒给,竟然沒有反應(yīng)了?block中的2
沒有被打印出來骏全,viewDidLoad()中的3
也沒有被打印出來苍柏。也就是說,block沒有得到執(zhí)行的機會姜贡,viewDidLoad也沒有繼續(xù)執(zhí)行下去试吁。為什么block不執(zhí)行呢?因為viewDidLoad也是執(zhí)行在主隊列的楼咳,它是正在被執(zhí)行的任務(wù)熄捍,也就是說,viewDidLoad()是主隊列的隊頭母怜。主隊列是串行隊列余耽,任務(wù)不能并發(fā)執(zhí)行,同時只能有一個任務(wù)在執(zhí)行糙申,也就是隊頭的任務(wù)才能被出列執(zhí)行宾添。我們現(xiàn)在被執(zhí)行的任務(wù)是viewDidLoad(),然后我們又將block入列到同一個隊列
柜裸,它比viewDidLoad()后入列缕陕,遵循先進先出的原理,它必須等到viewDidLoad()執(zhí)行完疙挺,才能被執(zhí)行扛邑。
但是,dispatch_sync函數(shù)的特性是铐然,等待block被執(zhí)行完畢蔬崩,才會返回,
因此搀暑,只要block一天不被執(zhí)行沥阳,它就一天不返回。我們知道自点,內(nèi)部方法不返回桐罕,外部方法是不會執(zhí)行下一行命令的。
不等到sync函數(shù)返回桂敛,viewDidLoad打死也不會執(zhí)行print End的語句功炮,因此,viewDidLoad()一直沒有執(zhí)行完畢术唬。block在等待著viewDidLoad()執(zhí)行完畢薪伏,它才能上,sync函數(shù)在等待著block執(zhí)行完畢粗仓,它才能返回嫁怀,viewDidLoad()在等待著sync函數(shù)返回,它才能執(zhí)行完畢借浊。這樣的三方循環(huán)等待關(guān)系眶掌,就造成了死鎖。
參考:理解GCD死鎖
關(guān)于GCD的例題:iOS面試題:阿里-P6一面-參考思路
總結(jié):同步串行必阻塞當前隊列(不一定會死鎖)
- 下面代碼輸出什么巴碗?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
//sleep(2);這里會導(dǎo)致打印5會后執(zhí)行
NSLog(@"5");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案:15243朴爬、15234
大廠面試題:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案:15243、152死鎖(沒有NSLog(@"4");
也是同樣的)
注意: #define DISPATCH_QUEUE_SERIAL NUL
- 下面代碼有可能輸出什么橡淆?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("com.differ.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
A: 1230789
B: 1237890
C: 3120798
D: 2137890
分析:
![](https://upload-images.jianshu.io/upload_images/2987980-b6ff3234f735ce80.png?imageMogr2/auto- orient/strip%7CimageView2/2/w/1240)
- 打印
1召噩、2、3
均在同一并發(fā)隊列中且打印1逸爵、2
為異步函數(shù)具滴,所以1、2师倔、3
的打印順序不是確定的构韵。 - 重點在于打印
3
的同步函數(shù),由于其在main
串行隊列中根據(jù)FIFO
,會先執(zhí)行完成打印3
然后再執(zhí)行打印0
疲恢。(同步函數(shù)會阻塞當前隊列)
- 執(zhí)行打印
0
后再將打印7凶朗、8、9
任務(wù)添加到并發(fā)隊列中显拳。所以3
一定在0
之前棚愤,7、8杂数、9
一定在0
之后宛畦,1、2
位置不確定揍移。
答案:AC
四次和、GCD底層分析
1、尋找GCD底層庫
添加dispatch_sync
符號斷點
運行代碼:
所以GCD
的底層源碼在libdispatch.dylib
庫中
GCD
源碼初始化方法libdispatch_init(void)
2那伐、隊列是如何創(chuàng)建的斯够?串行隊列和并發(fā)隊列創(chuàng)建時如何區(qū)分的?
隊列創(chuàng)建方法:dispatch_queue_create
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// dqai 創(chuàng)建:根據(jù)傳入的串行/并發(fā)參數(shù)創(chuàng)建
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
// Step 1: Normalize arguments (qos, overcommit, tq)
...
// Step 2: Initialize the queue
...
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
// 確定串行隊列/并發(fā)隊列的類名
if (dqai.dqai_concurrent) {
// OS_dispatch_queue_concurrent-拼接類名
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
...
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // alloc喧锦、確定isa指向的類名
//根據(jù)qai.dqai_concurrent區(qū)分是串行隊列還是并發(fā)隊列
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
dq->dq_label = label;
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
_dispatch_queue_priority_inherit_from_target(dq, tq);
_dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
}
從dqai
的創(chuàng)建方法中我們可以發(fā)現(xiàn)只要隊列創(chuàng)建傳入的參數(shù)不是DISPATCH_QUEUE_CONCURRENT
那就創(chuàng)建并發(fā)隊列
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { };
if (!dqa) return dqai;
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) { // null 默認
dqai.dqai_concurrent = true;
return dqai;
}
#endif
....
}
#define DISPATCH_QUEUE_CONCURRENT \
DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
_dispatch_queue_attr_concurrent)
通過隊列queue
的創(chuàng)建過程我們可以看到queue
也是一個對象
读规,存在alloc、init燃少、isa
等
DISPATCH_QUEUE_WIDTH_MAX
決定了當前隊列能開辟多大:
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
DISPATCH_QUEUE_WIDTH_POOL
-全局并發(fā)隊列寬度:4096-1
DISPATCH_QUEUE_WIDTH_MAX
- 一般并發(fā)隊列寬度: 4096-2
3束亏、異步函數(shù)底層分析:dispatch_async
#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
// 任務(wù)包裝器 - 保存 block
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
dispatch_async
方法中主要調(diào)用兩個方法:
_dispatch_continuation_init
和_dispatch_continuation_async
先介紹_dispatch_continuation_init
方法_dispatch_continuation_async
在下一篇文章分析
DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
dispatch_queue_class_t dqu, dispatch_block_t work,
dispatch_block_flags_t flags, uintptr_t dc_flags)
{
void *ctxt = _dispatch_Block_copy(work);
dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
if (unlikely(_dispatch_block_has_private_data(work))) {
dc->dc_flags = dc_flags;
dc->dc_ctxt = ctxt;
// will initialize all fields but requires dc_flags & dc_ctxt to be set
return _dispatch_continuation_init_slow(dc, dqu, flags);
}
dispatch_function_t func = _dispatch_Block_invoke(work);
if (dc_flags & DC_FLAG_CONSUME) {
func = _dispatch_call_block_and_release;//調(diào)用Block的func
}
return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init_f(dispatch_continuation_t dc,
dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f,
dispatch_block_flags_t flags, uintptr_t dc_flags)
{
pthread_priority_t pp = 0;
dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
dc->dc_func = f;// 保存調(diào)用Block的func
dc->dc_ctxt = ctxt;// 保存Block
// in this context DISPATCH_BLOCK_HAS_PRIORITY means that the priority
// should not be propagated, only taken from the handler if it has one
if (!(flags & DISPATCH_BLOCK_HAS_PRIORITY)) {
pp = _dispatch_priority_propagate();
}
_dispatch_continuation_voucher_set(dc, flags);
return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}
打印出Block
調(diào)用的堆棧:
因此的確是_dispatch_call_block_and_release
方法調(diào)用了Block