这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
1. GCD简介
在Mac OS x v10.6中引入的Grand Central Dispatch
是线程的另一种替代品,它允许您专注于需要执行的任务
,而不是线程管理
。使用GCD,您可以定义要执行的任务
并将其添加
到工作队列中,该队列处理在适当线程上的任务调度
。工作队列考虑了可用核心的数量和当前负载,以比使用线程更有效地执行任务。
GCD是纯 C 语言
,提供了非常多强大的函数。它的优势:
-
GCD 是苹果公司为
多核的并行
运算提出的解决方案 -
GCD 会自动利用更多的
CPU内核
(比如双核、四核) -
GCD 会
自动管理
线程的生命周期
(创建线程、调度任务、销毁线程) 程序员只需要告诉 -
GCD 想要执行什么任务,不需要
编写
任何线程管理代码
1.1 函数
任务使用 block
封装
* 任务的 block
没有参数也没有返回值
* 执行任务的函数\
- 异步
dispatch_async
* 不用等待
当前语句执行完毕,就可以执行下一条语句
* 会开启线程
执行 block
的任务
* 异步是多线程的代名词
- 同步
dispatch_sync
* 必须等待当前语句执行完毕
,才会执行下一条
语句
* 不会开启
线程
* 在当前执行 block
的任务
1.2 串行队列和并行队列
队列:执行任务的等待队列
,即用来存放任务的队列。队列是一种特殊的线性表
,遵循先进先出(FIFO)原则
,即新任务总是被插入到队尾
,而任务的读取从队首开始读取
。每读取一个任务,则动队列中释放一个任务。队列分为串行队列和并行队列
串行队列
:每次只执行一个
任务,要等上一个任务执行完成才能执行下一个任务,可以想象成一个串行电路
,朝着一个方向,也可以称为护犊子
,上一个任务没有走完,无法走到下一个任务。
//串行队列
dispatch_queue_t queue = dispatch_queue_create("keke", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("serial_k", NULL);//NULL表示默认串行队列
复制代码
并行队列
:每次可以执行多个
任务,不需要等待
上个任务完成就可以执行下一个任务,可以想象并行电路
,朝着一个方向遇到分流就会都去走。同时可以执行多个任务
。
//并行队列创建
dispatch_queue_t queue = dispatch_queue_create("kebei", DISPATCH_QUEUE_CONCURRENT);
复制代码
1.3 函数和队列
函数有同步函数和异步函数,队列有同步队列和异步队列,因此他们有4种情况。
1.3.1 同步函数串行队列
-(void)kb_testSyncFunctionSerialQueue
{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
sleep(2);
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务1");
});
dispatch_sync(serialQueue, ^{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务2");
});
}
复制代码
- 不会开启线程,在当前线程执行任务。
- 任务串行执行,一个接着一个。
- 会产生堵塞。
1.3.2 同步函数并发队列
-(void)kb_testSyncFunctionInConcurrentQueue
{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
sleep(2);
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务1");
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务2");
});
}
复制代码
- 不会开启线程,在当前线程执行任务。
- 任务串行执行,一个接着一个。
- 不产生堵塞
1.3.3 异步函数串行队列
-(void)kb_testAsyncFunctionInSerialQueue
{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
sleep(2);
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务1");
});
dispatch_async(serialQueue, ^{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务2");
});
}
复制代码
- 开启新的线程,新线程执行任务。
- 任务一个接着一个接着一个串行。
- 不产生堵塞。
1.3.4 异步函数并发队列
-(void)kb_testAsyncFunctionInConcurrentQueue
{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(2);
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"任务2");
});
}
复制代码
- 开启线程,在当前线程执行任务。
- 任务异步执行,没有顺序,和CPU调度有关。
- 不产生堵塞。
1.4 主队列和全局队列
- 主队列
专⻔用来在主线程上
调度任务的串行队列
不会开启线程
如果当前主线程正在有任务执行
,那么无论主队列中当前被添加
了什么任务,都不会被调度
dispatch_queue_t mainQueue = dispatch_get_main_queue();
复制代码
- 全局并发队列
为了方便程序员的使用,苹果提供了全局队列
全局队列是一个并发队列
在使用多线程
开发时,如果对队列没有特殊需求,在执行异步任务
时,可以直接使用全局队列
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
复制代码
1.4.1 主队列和同步函数
主队列 要执行NSLog
和同步block任务
。同步block
要等block中的打印任务
完成才可以同步
,但是block打印任务
又要等block同步
了才执行打印。因此相互等待
,造成死锁
。
死锁现象
- 主线程因为
同步函数
的原因先执行
任务。 - 主队列等着
主线程
的任务完成
在执行自己的任务
。 - 主队列和主线程
相互等待
造成死锁。
1.4.2 主队列和异步函数
不开辟
新线程,当前线程按顺序
执行
1.4.3 全局队列和同步函数
同步函数不开辟
新的线程,任务按顺序
执行
1.4.4 全局队列和异步函数
任务并发
执行,不按顺序,会开辟
新的线程。
2. GCD常见面试题
2.1 下面有几种类型的队列
//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("kb_serialQueue", NULL);
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
复制代码
2种,串行队列
和并行队列
。
主队列本质是串行队列
,定义了main的标识,线程编号是1.
全局队列的本质是并行队列
。
2.2 微博线程执行顺序面试题
下面的代码执行顺序
- (void)wbinterDemo{//
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.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
}
复制代码
答案是A|C
c
分析:在并发队列
中,异步函数
是没有执行顺序
的,1和2是可能先可能后,3同步函数
会执行玩才能执行下面的,因此123
是先执行,不分先后
;0是在同步之后
因此3完成之后才是0,7,8,9时异步执行但是都会在0之后
,顺序原因,7,8,9 本身并发没有先后顺序。因此123乱序-0 -789乱序
,0一定是在第4位,所以选A
和C
。
- 将上面的队列换成串行队列选什么
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
复制代码
选A,按顺序执行
,在串行队列中按照先进先出
的原则,异步函数会开辟一个
新的线程执行任务,同步函数
不会,但是在串行队列还是按FIFO
原则。
2.3 异步函数嵌套异步函数的调用顺序
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
复制代码
调用顺序为:1,5,2,4,3
. 分析如下:
- 主线程中按顺序执行
1->并发队列异步函数->5
,由于5比较简单先完成了5; - 在异步函数中
2->异步函数3->4
, 由于4比较简单先完成了4,之后是3; - 因此执行顺序打印结果是
1,5,2,4,3
添加sleep进行模拟:
- 延伸:如果将上面的队列改为串行队列
DISPATCH_QUEUE_SERIAL
,打印结果是什么?
打印结果是1,2,5,4,3
或1,5,2,4,3。
分析如下:
- 主线程按顺序执行
1->串行队列异步函数->5
,由于5比较简单先完成了5,但是有可能堵塞
会进入异步函称的block
,会先完成2,我在打印的过程出现了一次2在5
之前。 - 进入
异步函数的block
中,按顺序执行任务2->4->串行队列异步函数3
,由于4在串行队列所谓的主线程中,子线程要等主线程完成完成才会执行子线程
. - 可以添加
slepp(1)
来堵塞线程模拟
一般异步函数会开辟新的线程
,队列并发执行,子线程异步函数会开辟新的线程
,由于是并发
所以根据耗时多少
打印顺序按耗时时间来。
串行队列中,任务是按一个一个顺序执行
的,因此他们共用一个线程
,任务4在开辟的线程中执行
,完成后才会把线程给任务3
来执行任务,之后传给任务13
.这个过程不以时间长短来
,而已顺序来执行
。
2.4 异步函数嵌套同步函数的调用顺序
- (void)textDemo2{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"当前 线程 - %@", [NSThread currentThread]);
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
复制代码
执行顺序 1,5,2,3,4
。分析:
- 外层执行1->异步函数->5,在异步函数内部执行并发任务。
- 内部执行2->同步函数(堵塞线程)—> 4.
把并发队列改为串行队列则造成死锁
在异步函数block
中,同步函数3
完成才能执行4
,但是在同步队列中又要完成任务4就是大的block完成
才可以执行任务3
,相当于任务3和任务4
都要等对方完成它才能走
。
去掉任务4还是崩溃,因为这个大的block
无法执行,和是否执行任务无关,同步函数把线程锁住了。
3. 队列和函数的原理分析
我们知道队列分4种,主队列
,全局队列
,串行队列
,并发队列
。但是实际上就是2种
,上面也分析了。那么队列是怎么实现的呢?我们需要查看libdispatch
源码下载链接
3.1 主队列
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
复制代码
主队列是自动创建的,调用main()
之前的主线程,主队列是用来在应用程序上下文中进行交互的。
继续看下DISPATCH_GLOBAL_OBJECT
定义的宏
#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
复制代码
第一个参数是类型,我们传的是dispatch_queue_main_t
,第二参数是对象我们传的是_dispatch_main_q
。由于主队列在main()
函数之前就创建好了。我们
搜索_dispatch_main_q =
的创建的地方
// 6618342 Contact the team that owns the Instrument DTrace probe before
// renaming this symbol
//静态变量修饰表示只会创建一次,是结构体类型
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),//
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,//状态
.dq_label = "com.apple.main-thread",//名字
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),//标识
.dq_serialnum = 1,//编号
};
复制代码
- 我们还有一种便捷的方法
我们打印4种队列,其中<#const char * _Nullable label#>
就是队列的名字,所以主队列应该就是默认的com.apple.main-thread
的主队列名字,去libdispatch
搜索
和我们之前分析的一样。
3.2 全局队列
全局队列是一个并发队列
,通常传入一个队列的优先级
,和flag(线程过载)标识。
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t priority, uintptr_t flags)
{
dispatch_assert(countof(_dispatch_root_queues) ==
DISPATCH_ROOT_QUEUE_COUNT);
if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
return DISPATCH_BAD_INPUT;//带有 overcommit 的队列表示每当有任务提交时,系统都会新开一个线程处理,这样就不会造成某个线程过载(overcommit)。
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);//获取优先级
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == QOS_CLASS_MAINTENANCE) {
qos = DISPATCH_QOS_BACKGROUND;//要求这些工作优先于其他工作运行。使用这个QOS类表明工作应该以最节能和最高效的方式运行。
} else if (qos == QOS_CLASS_USER_INTERACTIVE) {
qos = DISPATCH_QOS_USER_INITIATED;//这类工作的优先级要求低于关键的用户交互工作,但相对高于系统上的其他工作。这不是用于大型任务的节能QOS类。它的使用应该限制在足够短的持续时间内,这样用户在等待结果时就不太可能切换任务。典型的用户发起的工作将通过显示占位符内容或模态用户界面来指示进度。
}
#endif
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
复制代码
继续_dispatch_get_root_queue
DISPATCH_ALWAYS_INLINE DISPATCH_CONST
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");//优先级在范围内
}
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
复制代码
系统创建队列4-15
struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
__VA_ARGS__ \
}
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
.dq_label = "com.apple.root.maintenance-qos",
.dq_serialnum = 4,
),
_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.maintenance-qos.overcommit",
.dq_serialnum = 5,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
.dq_label = "com.apple.root.background-qos",
.dq_serialnum = 6,
),
_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.background-qos.overcommit",
.dq_serialnum = 7,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
.dq_label = "com.apple.root.utility-qos",
.dq_serialnum = 8,
),
_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.utility-qos.overcommit",
.dq_serialnum = 9,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
.dq_label = "com.apple.root.user-initiated-qos",
.dq_serialnum = 12,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-initiated-qos.overcommit",
.dq_serialnum = 13,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
.dq_label = "com.apple.root.user-interactive-qos",
.dq_serialnum = 14,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.user-interactive-qos.overcommit",
.dq_serialnum = 15,
),
};
复制代码
3.3 串行队列和并发队列
3.3.1 dispatch_queue_t
日常开发中使用不论是串行队列还是并发队列都是dispatch_queue_t
类型。
typedef struct dispatch_queue_s *dispatch_queue_t;//是dispatch_queue_s类型的结构体
//继续查看
#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
_DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
/* LP64 global queue cacheline boundary */ \
unsigned long dq_serialnum; \
const char *dq_label; \
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
const uint16_t dq_width, \
const uint16_t __dq_opaque2 \
); \
dispatch_priority_t dq_priority; \
union { \
struct dispatch_queue_specific_head_s *dq_specific_head; \
struct dispatch_source_refs_s *ds_refs; \
struct dispatch_timer_source_refs_s *ds_timer_refs; \
struct dispatch_mach_recv_refs_s *dm_recv_refs; \
struct dispatch_channel_callbacks_s const *dch_callbacks; \
}; \
int volatile dq_sref_cnt
struct dispatch_queue_s {
DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
/* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;
//主要是_DISPATCH_QUEUE_CLASS_HEADER?继续查看
#if OS_OBJECT_HAVE_OBJC1
#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
DISPATCH_OBJECT_HEADER(x); \
DISPATCH_UNION_LE(uint64_t volatile dq_state, \
dispatch_lock dq_state_lock, \
uint32_t dq_state_bits \
); \
__pointer_sized_field__
#else
#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
DISPATCH_OBJECT_HEADER(x); \
__pointer_sized_field__; \
DISPATCH_UNION_LE(uint64_t volatile dq_state, \
dispatch_lock dq_state_lock, \
uint32_t dq_state_bits \
)
//继续查看DISPATCH_OBJECT_HEADER ?
#define DISPATCH_OBJECT_HEADER(x) \
struct dispatch_object_s _as_do[0]; \
_DISPATCH_OBJECT_HEADER(x)
\
//继续查看_DISPATCH_OBJECT_HEADER?
#endif
#define _DISPATCH_OBJECT_HEADER(x) \
struct _os_object_s _as_os_obj[0]; \
OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
struct dispatch_##x##_s *volatile do_next; \
struct dispatch_queue_s *do_targetq; \
void *do_ctxt; \
union { \
dispatch_function_t DISPATCH_FUNCTION_POINTER do_finalizer; \
void *do_introspection_ctxt; \
}
//继续查看OS_OBJECT_STRUCT_HEADER?
#define OS_OBJECT_STRUCT_HEADER(x) \
_OS_OBJECT_HEADER(\
const void *_objc_isa, \
do_ref_cnt, \
do_xref_cnt); \
const struct x##_vtable_s *do_vtable
//继续查看_OS_OBJECT_HEADER?
#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
isa; /* must be pointer-sized and use __ptrauth_objc_isa_pointer */ \
int volatile ref_cnt; \
int volatile xref_cnt
复制代码
经过剥茧抽丝最终找到dispatch_queue_t
结构体储存来我们定义的lable
,priority
、specific
等以及isa
,引用计数
等。
3.3.2 dispatch_queue_create
我们上面知道了队列本质是结构体
,储存了我们定义的label
,priority
等信息。继续看下怎么创建的
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)
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
\
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//处理规范化参数,优先级,线程过载,队列类型
\
dispatch_qos_t qos = dqai.dqai_qos;//处理优先级
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
}
if (qos == DISPATCH_QOS_MAINTENANCE) {
dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS
\
_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
if (tq->do_targetq) {
DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
"a non-global target queue");
}
}
//处理队列类型 优先级下是否线程过载重新开辟新的线程
if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
// Handle discrepancies between attr and target queue, attributes win
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
overcommit = _dispatch_queue_attr_overcommit_enabled;
} else {
overcommit = _dispatch_queue_attr_overcommit_disabled;
}
}
if (qos == DISPATCH_QOS_UNSPECIFIED) {
qos = _dispatch_priority_qos(tq->dq_priority);
}
tq = NULL;
} else if (tq && !tq->do_targetq) {
// target is a pthread or runloop root queue, setting QoS or overcommit
// is disallowed
if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
"and use this kind of target queue");
}
} else {
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
// Serial queues default to overcommit!
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
}
}
if (!tq) {
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
if (unlikely(!tq)) {
DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
}
}
//
// Step 2: Initialize the queue
//
\
if (legacy) {
// if any of these attributes is specified, use non legacy classes
if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
legacy = false;
}
}
\
const void *vtable;//拼接队列名称
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
vtable = DISPATCH_VTABLE(queue_concurrent);//队列类型
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
switch (dqai.dqai_autorelease_frequency) {
case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
dqf |= DQF_AUTORELEASE_NEVER;
break;
case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
dqf |= DQF_AUTORELEASE_ALWAYS;
break;
}
if (label) {//拼接队列名字
const char *tmp = _dispatch_strdup_if_mutable(label);
if (tmp != label) {
dqf |= DQF_LABEL_NEEDS_FREE;
label = tmp;
}
}
\
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));//开辟队列内存
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));//队列初始化
\
dq->dq_label = 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;//队列处理
}
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
\
复制代码
主要分为2步
- 第一步:
规范化参数
,处理队列优先级
(有6个:userInteractive>default>unspecified>userInitiated>utility>background),以及过载线程
(支持overcommit的队列在创建队列时无论系统是否有足够的资源
都会重新开一个线程)。 - 第二步 :
处理队列名字
,队列类型
(串行还是并行),初始化队列
,绑定
关系,队列处理
。
- 【第一步】
dqai
创建
//
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
复制代码
主要是创建dispatch_queue_attr_info_t
类型用来储存队列的优先级,过载线程等信息。
- 【第二步】设置队列相关联的属性,例如队列
优先级qos
等 - 【第三步】通过
DISPATCH_VTABLE
拼接队列名称 - 【第四步】初始化
alloc
+init
,队列类型设置,isa指向,DQF_WIDTH(width)类型
alloc:
void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
const struct dispatch_object_vtable_s *_vtable = vtable;
dispatch_object_t dou;
dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);//isa指向
dou._do->do_vtable = vtable;//设置类型
return dou._do;
#else
return _os_object_alloc_realized(vtable, size);
#endif
}
复制代码
init:
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
uint16_t width, uint64_t initial_state_bits)
{
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
dispatch_queue_t dq = dqu._dq;
dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);
if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}
dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);//串行还是并发width本身就是并发数的个数,对于串行队列是1而对于并发队列是不限制的
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}
复制代码
- 【第五步】 创建的队列进行处理
_dispatch_trace_queue_create
进行一些处理实际上_dispatch_introspection_queue_create
dispatch_queue_class_t
_dispatch_introspection_queue_create(dispatch_queue_t dq)
{
dispatch_queue_introspection_context_t dqic;
size_t sz = sizeof(struct dispatch_queue_introspection_context_s);//计算大小
if (!_dispatch_introspection.debug_queue_inversions) {
sz = offsetof(struct dispatch_queue_introspection_context_s,
__dqic_no_queue_inversion);
}
dqic = _dispatch_calloc(1, sz);
dqic->dqic_queue._dq = dq;
if (_dispatch_introspection.debug_queue_inversions) {
LIST_INIT(&dqic->dqic_order_top_head);
LIST_INIT(&dqic->dqic_order_bottom_head);
}
dq->do_introspection_ctxt = dqic;
_dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);
LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);
_dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);
DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);
if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {
_dispatch_introspection_queue_create_hook(dq);
}
return upcast(dq)._dqu;
}
复制代码
_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info
中可以看出,与我们自定义的类还是有所区别的,创建队列
在底层的实现是通过模板创建
的
static inline dispatch_introspection_queue_s
_dispatch_introspection_workloop_get_info(dispatch_workloop_t dwl)
{
uint64_t dq_state = os_atomic_load2o(dwl, dq_state, relaxed);
dispatch_introspection_queue_s diq = {
.queue = dwl->_as_dq,
.target_queue = dwl->do_targetq,
.label = dwl->dq_label,
.serialnum = dwl->dq_serialnum,
.width = 1,
.suspend_count = 0,
.enqueued = _dq_state_is_enqueued(dq_state),
.barrier = _dq_state_is_in_barrier(dq_state),
.draining = 0,
.global = 0,
.main = 0,
};
return diq;
}
复制代码
3.4 同步函数原理分析
源码如下:
继续查看
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {//同步函数
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);//栅栏函数初始化
}
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);//死锁
}
if (unlikely(dq->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);//处理当前信息
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//并发队列则调用
}
复制代码
流程是什么呢?我们添加对应的符号断点进行查看
查看下是否width ==1
则是同步函数调用_dispatch_barrier_sync_f
并发队列则调用_dispatch_sync_invoke_and_complete
。先看下_dispatch_barrier_sync_f
先获取线程id处理死锁的情况,因次先查看下死锁的情况:
继续看__DISPATCH_WAIT_FOR_QUEUE__
进入_dq_state_drain_locked_by
继续进入
判断将要调度的
和当前等待的队列
是不是同一个,如果相同则返回YES,产生死锁DISPATCH_CLIENT_CRASH
。如果没有产生死锁,则执行 _dispatch_trace_item_pop()
出队列执行。如何执行调度呢,需要看一下_dispatch_sync_invoke_and_complete_recurse
查看下_dispatch_sync_function_invoke_inline
:
查看下callout
:
可以比较清楚的看到最终执行f函数
,这个就是外界传过来的回调block
。
同步函数主要是三个步骤
- 将任务压入队列:
_dispatch_thread_frame_push
- 执行任务的block回调:
_dispatch_client_callout
- 将任务出队:
_dispatch_thread_frame_pop
3.5 异步函数原理分析
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;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);//处理block回调 work
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//并发处理
}
复制代码
查看任务包装_dispatch_continuation_init
我们如何确定走的那个return
,继续用符号断点
继续查看_dispatch_continuation_init_f
保存func
到dispatch_continuation_t
中,在把dc
赋值到dispatch_qos_t
中。
- _dispatch_continuation_async 处理
核心是dx_push宏定义
,查看
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
复制代码
本质是调用dx_vtable
的dq_push
(其实就是调用对象的do_push
),进一步查看 dq_push
不同的队列执行不同的dq_push
,我们代码中队列用的全局队列,添加符号断点验证下。
继续查看_dispatch_root_queue_push
查看
我们可以看到,我们装入到自定义的任务都被扔到其挂靠的root队列
里去了,因此我们我们自己创建的队列只是一个代理人身份,继续查看 _dispatch_root_queue_poke
查看_dispatch_root_queue_poke_slow
:
先看下_dispatch_root_queues_init
的实现:
static inline void
_dispatch_root_queues_init(void)
{
dispatch_once_f(&_dispatch_root_queues_pred, NULL,
_dispatch_root_queues_init_once);
}
复制代码
dispatch_once_f
是单例,具体实现后面再看。_dispatch_root_queues_init_once
的实现
查看下_dispatch_workloop_worker_thread
查看 _dispatch_worker_thread2
查看 _dispatch_root_queue_drain
查看 _dispatch_continuation_pop_inline
首先看了有没有vtable(_dispatch_object_has_vtable
),这里解释了为什么dispatch_barrier_async
尽管主要流程和dispatch_async
一模一样但是无法应用到全局队列的原因,因为全局队列没有v_table结构会直接像dispatch_async
一样执行
查看_dispatch_continuation_invoke_inline
:
真正执行block回调,查看_dispatch_client_callout
,上面在_dispatch_continuation_init
中对block进行包装,它在dx_push
调用时包装进了qos
。
我们也可以打印下堆栈信息验证下流程
其block回调执行的调用路径为:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release
异步函数的底层较为复杂,其中的分支比较多
,但是思路还是比较明确的,用一个链表保存所有的block
,在队列本身维护了一个链表新加入的block
放在后面,先进先出
,之后在底层的线程池中,依次取出block
并执行。
4. 总结
GCD
依赖于队列,队列分为串行队列
和并发队列
,常用的4种队列,系统创建的主队列(系统创建队列1
)和全局队列(系统创建队列4-15
),队列所占内存大小一般会有大概64*1024
大小。- 函数有2种,队列有有2种因此他们有
4种组合
大的方向,同步函数会堵塞
队列,异步函数不会,串行队列FIFO
先进先出,并发队列可以执行多个
任务,不需要等待。 - 死锁本质是
线程执行block回调的队列
和在等待的队列
是同一个队列
造成死锁崩溃。 - 队列本质是
dispatch_queue_t
的结构体对象,我们定义的lable
,priority
、specific
等以及isa
,引用计数
,队列创建大致2步,第一步:规范化参数
,处理队列优先级
,支持overcommit
的队列在创建队列时无论系统是否有足够的资源
都会重新开一个线程;第二步:线程名字,类型处理,线程初始化
,队列处理。 - 同步函数原理主要3步,任务
加入
队列,执行
任务回调,任务出列
。 - 异步函数原理:队列本身用一个
链表保存所有的任务
block,遵循先进先出
,之后在底层线程池依次取出block执行
。