目录
一、GCD简介
- 什么是GCD:
- 全称是Grand Central Dispatch
- 纯C语言,提供了非常多强大的函数
- 将任务添加到队列,并且指定执行任务的函数
- GCD的优势:
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
二、任务、函数、队列
- 任务使用
block
封装 - 任务的
block
没有参数也没有返回值 - 执行任务的函数
- 异步
dispatch async
函数
不用等待当前语句执行完毕,就可以执行下一条语句
会开启线程执行block
的任务
异步是多线程的代名词
- 异步
- 同步
dispatch sync
函数
必须等待当前语句执行完毕,才会执行下一条语句
不会开启线程
在当前线程执行block
的任务
- 队列
队列用来调度任务,任务都做线程中执行
串行队列和并发队列都符合FIFO原则
(先进先出并不是先进先执行完成)
- 队列和函数
异步函数+并发队列 开启多条新的子线程、并发
异步函数+串行队列 为所有串行队列中的任务仅开启一个新的子线程、串行
同步函数+并发队列 同步函数中均不开启新的子线程、串行
同步函数+串行队列 同上...
- 主队列和全局队列
主队列
- 专门用来在
主线程
上调度任务的串行队列
- 不会开启线程
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
dispatch get main queue();
全局队列
- 为了方便程序员的使用,苹果提供了全局队列dispatch_get_global queue(0, 0)
- 全局队列是一个
并发队列
- 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
三、GCD相关面试题
- 下面代码输出什么?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
这个程序就是典型的死锁,可以看到,只打印了1
,就再也没有响应了,已经造成了GCD死锁。为什么会这样呢?让我们来解读一下这段程序的运行顺序:首先会打印1
,然后将主队列和一个block传入GCD同步函数dispatch_sync中,等待sync函数执行,直到它返回,才会执行打印3
的语句。可是,竟然没有反应了?block中的2
没有被打印出来,viewDidLoad()中的3
也没有被打印出来。也就是说,block没有得到执行的机会,viewDidLoad也没有继续执行下去。为什么block不执行呢?因为viewDidLoad也是执行在主队列的,它是正在被执行的任务,也就是说,viewDidLoad()是主队列的队头。主队列是串行队列,任务不能并发执行,同时只能有一个任务在执行,也就是队头的任务才能被出列执行。我们现在被执行的任务是viewDidLoad(),然后我们又将block入列到同一个队列
,它比viewDidLoad()后入列,遵循先进先出的原理,它必须等到viewDidLoad()执行完,才能被执行。
但是,dispatch_sync函数的特性是,等待block被执行完毕,才会返回,
因此,只要block一天不被执行,它就一天不返回。我们知道,内部方法不返回,外部方法是不会执行下一行命令的。
不等到sync函数返回,viewDidLoad打死也不会执行print End的语句,因此,viewDidLoad()一直没有执行完毕。block在等待着viewDidLoad()执行完毕,它才能上,sync函数在等待着block执行完毕,它才能返回,viewDidLoad()在等待着sync函数返回,它才能执行完毕。这样的三方循环等待关系,就造成了死锁。
参考:理解GCD死锁
关于GCD的例题:iOS面试题:阿里-P6一面-参考思路
总结:同步串行必阻塞当前队列(不一定会死锁)
- 下面代码输出什么?
- (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);这里会导致打印5会后执行
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://img.haomeiwen.com/i2987980/b6ff3234f735ce80.png?imageMogr2/auto- orient/strip%7CimageView2/2/w/1240)
- 打印
1、2、3
均在同一并发队列中且打印1、2
为异步函数,所以1、2、3
的打印顺序不是确定的。 - 重点在于打印
3
的同步函数,由于其在main
串行队列中根据FIFO
,会先执行完成打印3
然后再执行打印0
。(同步函数会阻塞当前队列)
- 执行打印
0
后再将打印7、8、9
任务添加到并发队列中。所以3
一定在0
之前,7、8、9
一定在0
之后,1、2
位置不确定。
答案:AC
四、GCD底层分析
1、寻找GCD底层库
添加dispatch_sync
符号断点
运行代码:
所以GCD
的底层源码在libdispatch.dylib
库中
GCD
源码初始化方法libdispatch_init(void)
2、队列是如何创建的?串行队列和并发队列创建时如何区分的?
队列创建方法: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 创建:根据传入的串行/并发参数创建
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;
// 确定串行队列/并发队列的类名
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指向的类名
//根据qai.dqai_concurrent区分是串行队列还是并发队列
_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
的创建方法中我们可以发现只要队列创建传入的参数不是DISPATCH_QUEUE_CONCURRENT
那就创建并发队列
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
的创建过程我们可以看到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
-全局并发队列宽度:4096-1
DISPATCH_QUEUE_WIDTH_MAX
- 一般并发队列宽度: 4096-2
3、异步函数底层分析: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;
// 任务包装器 - 保存 block
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
dispatch_async
方法中主要调用两个方法:
_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;//调用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;// 保存调用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
调用的堆栈:
因此的确是_dispatch_call_block_and_release
方法调用了Block
网友评论