开局偷一张图手动目录
- 认识队列
队列的结构- 队列的产生
主队列
全局队列
创建的队列
管理队列
代码版本
- dispatch version : #define DISPATCH_API_VERSION 20181008
- 开源版本:libdispatch-1173.60.1
认识队列
说到GCD,一定离不开队列。
先看队列是一个什么东西。打印队列看看其包含那些信息(lldb打印)
dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_queue_t testSerQ = dispatch_queue_create("text.com.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t testConQ = dispatch_queue_create("text.com.queue_con", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 打印结果
mainQ: <OS_dispatch_queue_main: com.apple.main-thread[0x10abffb00] = { xref = -2147483648, ref = -2147483648, sref = 1, target = com.apple.root.default-qos.overcommit[0x10abfff80], width = 0x1, state = 0x001ffe9000000300, dirty, in-flight = 0, thread = 0x303 }>
testSerQ: <OS_dispatch_queue_serial: text.com.queue[0x60000212e080] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos.overcommit[0x10abfff80], width = 0x1, state = 0x001ffe2000000000, in-flight = 0}>
testConQ: <OS_dispatch_queue_concurrent: text.com.queue_con[0x60000212e100] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos[0x10abfff00], width = 0xffe, state = 0x0000041000000000, in-flight = 0}>
globalQ: <OS_dispatch_queue_global: com.apple.root.default-qos[0x10abfff00] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}>
队列的结构
源码查看dispatch_queue_t
结构
typedef struct dispatch_queue_s *dispatch_queue_t;
底层源码看多了就知道 xxx_t 是C语言结构体的写法,搜索 dispatch_queue_t {
struct dispatch_queue_s {
DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
/* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;
DISPATCH_QUEUE_CLASS_HEADER 是一个宏定义,我们用替换的方法最后得到便于理解的 结构
struct dispatch_queue_s {
struct _os_object_s _as_os_obj[0];
const struct dispatch_queue_vtable_s *do_vtable, // dispatch_queue_s的操作函数:dispatch_queue_vtable_s类型的结构体
int volatile do_ref_cnt; //引用计数
int volatile do_xref_cnt //外部引用计数
struct dispatch_queue_s *volatile do_next; //链表的next
struct dispatch_queue_s *do_targetq; //目标队列,GCD允许我们将一个队列放在另一个队列里执行任务
void *do_ctxt; //上下文,用来存储线程池相关数据,比如用于线程挂起和唤醒的信号量、线程池尺寸等
void *do_finalizer
void *__dq_opaque1;
DISPATCH_UNION_LE(uint64_t volatile dq_state,
dispatch_lock dq_state_lock,
uint32_t dq_state_bits
)
unsigned long dq_serialnum; //队列的序列号
const char *dq_label; // 队列的名字
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,
const uint16_t dq_width, //最大并发数:主线程/串行中这个值为1
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
} DISPATCH_ATOMIC64_ALIGN;
这里需要说明的几个参数:
- do_targetq(目标队列):非全局队列类型(创建的队列、主队列、管理队列),都需要压入到根队列处理。(全局队列本身就是根队列);
这个目标队列的目的就是允许我们将一个队列放在另一个队列里执行任务- dq_width: 理想状态下,可以有多少任务可以同时执行(串行为1、全局为0xfff(0x1000ull - 1)、并行为0xffe(0x1000ull - 2))
- dq_serialnum : 队列序号 (主队列为1)
- do_ref_cnt:引用计数,主队列和全局队列不需要考虑创建和释放,所以 是INT_MAX,自己创建的队列(串行、并发)都有生命周期的(创建、释放的过程),所以创建之后是1
队列的产生
主队列、全局队列 我们在想使用的时候,可以直接获取到,那么他们是在什么时机被创建的?
先了解libdispatch_init(void)
函数
这个函数的作用和调起时机类似于 object_init() / - (instancetype)init
方法 ,只是这个不由开发者调用,由系统调起。作用是初始化数据(做一些准备工作)。
void
libdispatch_init(void)
{
......
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
void
_dispatch_introspection_init(void)
{
.....
for (size_t i = 0; i < DISPATCH_ROOT_QUEUE_COUNT; i++) {
_dispatch_trace_queue_create(&_dispatch_root_queues[i]); // 初始化root_q[] count = 12
}
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
_dispatch_trace_queue_create(_dispatch_mgr_q.do_targetq);
#endif
_dispatch_trace_queue_create(&_dispatch_main_q); // 初始化main_q
_dispatch_trace_queue_create(&_dispatch_mgr_q); // 初始化mgr_q
}
主队列
直接搜索dispatch_get_main_queue(void)
或者用 dq_label 的名字com.apple.main-thread
在源码中搜索 (替换掉宏定义)
struct dispatch_queue_static_s _dispatch_main_q = {
.do_vtable = DISPATCH_VTABLE(queue_main),
.do_ref_cnt = INT_MAX,
.do_xref_cnt = INT_MAX
.do_targetq = _dispatch_get_default_queue(true),
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) | // (0x0020000000000000ull - 1) << 41
DISPATCH_QUEUE_ROLE_BASE_ANON, // 0x0000001000000000ull
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1), //0x00040000 | 1
.dq_serialnum = 1,
};
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
// return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
return ((__bridge dispatch_queue_main_t)&(_dispatch_main_q))
}
C语言能力有限,只能拆分到这一步。。。。。。
从上面的代码可以看到,主队列是随用随取。而不是用的时候再创建。再dispatch_init的时候 就有了默认值
全局队列
//identifier: 优先级 通常写:DISPATCH_QUEUE_PRIORITY_DEFAULT
//flags: 保留字段 通常写:0
dispatch_queue_global_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority); // 4
......
// qos = 0; flag = 0 ; DISPATCH_QUEUE_OVERCOMMIT = 0x2ull
// flag & DISPATCH_QUEUE_OVERCOMMIT 【与运算 (同时为“1”,结果才为“1”,否则为0)】 0 & 2 = 0
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
...
// 获取下标为 [2 * (4 - 1) + 0] 的root队列 即 _dispatch_root_queues[6]
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
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,
),
};
其中,参数 flags 是一个苹果予保留字段,通常我们传0,(传入1 得到 null,传入2可以正常得到另外一个队列)。
上面代码可以看到,系统定义了 12个不同的root队列(serialnum = 4-15)。但是一般我们用的是 serialnum = 10的全局队列。
创建的队列
创建的队列 分为串行队列
和并发队列
。
具体的创建过程,从源码来分析
贴上部分源码
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)
//
...... //初始化参数的一些操作
//
// Step 2: Initialize the queue
//
......
// 创建(alloc)/构造(init)
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));
// label 赋值
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;
}
.....
return _dispatch_trace_queue_create(dq)._dq;
}
创建队列的2个步骤
- 1、Step 1: Normalize arguments (规范化参数--- 优先级(qos)、是否支持过载(overcommit)、目标队列(tq))。相当于是先做准备工作
- 2、Step 2: Initialize the queue : 实例化队列(创建、实例化、赋值)
参数赋值过程:
- overcommit: 是否支持过载(支持overcommit的队列在创建队列时无论系统是否有足够的资源都会重新开一个线程)
串行队列默认支持过载
// Serial queues default to overcommit! overcommit = dqai.dqai_concurrent ? _dispatch_queue_attr_overcommit_disabled : //2 _dispatch_queue_attr_overcommit_enabled; //1
- width:在构造(init)里面进行赋值(concurrent ?
0x1000ull - 2: 1)- serialnum: 在构造(init)里面赋值 dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);- priority :优先级
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; }
- target: 目标队列 。非全局队列的队列类型(并发、主队列、串行),都需要压入到全局队列处理。(全局队列的targetq 为nil);
tq = _dispatch_get_root_queue( qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // qos == 0 DISPATCH_QOS_UNSPECIFIED == 0 结果就是 qos == DISPATCH_QOS_DEFAULT(4) overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; // bool 类型 串行为1 并发为0 } // 按照上面全局队列的root_queue[] 的下标计算 // 串行 [2 * (4 - 1) + 1] ; 并发 [2 * (4 - 1) + 0] // 串行 dq_serialnum = 11; 并发:dq_serialnum = 10
管理队列
上面贴全局队列的时候,发现 dq_serialnum = 4 - 15,主队列是 1 那么编号2、3为什么不用?
其实用了,只是没有公开 。
管理队列是GCD的内部队列,不对外公开。从名字上看,这个队列应该是用来扮演管理的角色,GCD定时器就用到了管理队列。
全局搜索 4 - 15
,可以看到
extern struct dispatch_queue_static_s _dispatch_mgr_q; // serial 2
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
extern struct dispatch_queue_global_s _dispatch_mgr_root_queue; // serial 3
#endif
extern struct dispatch_queue_global_s _dispatch_root_queues[]; // serials 4 - 15
_dispatch_mgr_q
管理队列 标号 2
_dispatch_mgr_root_queue
管理根队列 标号3; 是管理队列的目标队列
_dispatch_root_queues []
根队列数组 标号 4- 15
struct dispatch_queue_static_s _dispatch_mgr_q = {
.do_vtable = DISPATCH_VTABLE(name),
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.do_ctxt = (void *)-1,
.do_targetq = _dispatch_mgr_root_queue._as_dq,
.dq_label = "com.apple.libdispatch-manager",
.dq_atomic_flags = DQF_WIDTH(1),
.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
DISPATCH_PRIORITY_SATURATED_OVERRIDE,
.dq_serialnum = 2,
};
struct dispatch_queue_global_s _dispatch_mgr_root_queue = {
.do_vtable = DISPATCH_VTABLE(queue_global),
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE,
.do_ctxt = &_dispatch_mgr_root_queue_pthread_context,
.dq_label = "com.apple.root.libdispatch-manager",
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
DISPATCH_PRIORITY_SATURATED_OVERRIDE,
.dq_serialnum = 3,
.dgq_thread_pool_size = 1,
};
网友评论