iOS底层-多线程之GCD(上)
iOS底层-多线程之GCD(上)
前言
说到多线程
,我们肯定就不会忽视GCD
,因为它用法比较简洁,Api
也比较易懂,对于处理多个任务等都是比较简单的,接来下将对GCD
进行总结和探究。
简介
GCD
全称是Grand Central Dispatch
,纯C
语言Api
,提供了非常多的强大函数
GCD
优势:
GCD
是苹果公司为多核的并行运算
提出的解决方案
GCD
会自动利用更多的CPU
内核(如:双核、四核)
GCD
会自动管理
线程的生命周期:创建线程
、调度任务
、销毁线程
,程序员只需要告诉GCD
想要执行什么任务,不需要编写任何线程管理代码
函数
- 任务:
GCD
的任务使用block
封装,block
中没有参数
也没有返回值
执行任务的函数:
- 异步
dispatch_async
:不用等待当前语句执行完毕,就可以执行下一条语句- 会开启线程执行
block
的任务 - 异步是多线程的代名词
- 会开启线程执行
- 同步
dispatch_sync
:必须等待当前语句执行完毕,才会执行下一条语句- 不会开启线程
- 在当前线程执行
block
任务
队列
队列分为串行队列
和并行队列
,他们是一个数据结构
,都遵循FIFO(先进先出)
原则
串行队列
- 串行队列在同一时间只能执行一个任务,如图所示:
- 在根据
FIFO
原则先进先出,所以后面的任务必须等前面的任务执行完毕
才能执行,就导致串行队列是顺序执行
的
并行队列
- 并行队列是一次可以调度多个任务,但并不一定都能执行,线程的状态必须是
runable
时才能执行,所以先调度不一定先执行:
- 如果可以看出,并行在同一时间能执行多个任务
案例分析
- 并发异步任务
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("wushuang.concurrent", DISPATCH_QUEUE_CONCURRENT); //并发队列
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
执行顺序是什么呢,分析如下:
- 首先
1
在主线程且顺序执行,所以1最新打印
- 然后进入
dispatch_async
异步函数的block
块,块里先执行2
,同步函数会阻塞4
的打印,所以快中3在2和4中间
5
是看异步函数执行的速度,有可能在1
后面,也可能在2
或3
或4
后面
- 首先
所以可能的结果是
15234
、12534
、12354
、12345
- 串行异步任务
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("wushuang.serial", NULL); //串行队列
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- 这个顺序回是怎样的呢,再来分析下
- 首先
1
先执行 - 然后执行
dispatch_async
块中的代码,块中的同步函数次时与第一种不一样,根据串行队列FIFO原则
且是顺序执行,所以3
执行的前提是dispatch_async
函数执行完,但dispatch_async
函数执行完的前提是块中dispatch_sync
及之后代码能执行完,于是就出现了相互等待
,造成了死锁
- 首先
函数与关系
4种组合
函数与队列可以分为四种组合异步函数串行队列
、并发队列异步函数
、同步函数并发队列
、同步函数串行队列
- 异步函数串行队列:
开启
线程,任务一个接着一个
- 异步函数串行队列:
- 异步函数并发队列:
开启
线程,在当前线程执行任务,任务执行没有顺序
,和cpu
调度有关
- 异步函数并发队列:
- 同步函数并发队列:
不会开启
线程,在当前线程执行任务,任务一个接着一个
- 同步函数并发队列:
- 同步函数串行队列:
不会开启
线程,在当前线程执行任务,任务一个接着一个执行,会产生阻塞
- 同步函数串行队列:
主队列和全局队列
- 主队列:专门在
主线程
上调度任务的串行队列
,不会开启线程
,如果当前主线程正在执行任务,那么无论主队列中当前被添加了什么任务,都不会被调度dispatch_get_main_queue()
- 全局队列:为了方便程序员的使用,苹果提供了全局队列
dispatch_get_global_queue(0,0)
,全局队列是并发队列
,在使用多线程时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列
案例
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"会来吗?");
});
}
- 在
ViewDidLoad
中执行主线程同步任务
,那么会打印吗?结果产生死锁
- 分析:因为
ViewDidLoad
,在主线程上,主线程上调度任务的是主队列,主队列遵循FIFO
原则,而要执行该同步任务也在主队列执行
,所以必须等ViewDidLoad
函数执行完才能执行,而ViewDidLoad
函数执行完的前提是该同步任务执行完
,所以就产生了相互等待
,产生死锁
图解总结
队列源码分析
根据队列的介绍,我们知道有2种
队列:串行
和并发
,其中主队列
是特殊的串行
队列,全局队列
是特殊的并发
队列,那么在他们是怎么区分的呢?我们去打印堆栈看看:
- 堆栈中显示了个新的库
libdispatch
,先下载libdispatch-1271.120.2
主队列
在源码中搜索dispatch_get_main_queue
:
/
The main queue is meant to be used in application context to interact with the main thread and the main runloop.
Returns the main queue. This queue is created automatically on behalf of the main thread before main() is called.
*/
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
根据注释可知:
- 主队列与程序的
主线程
和runloop
进行交互 - 主队列在
main()
之前程序自动创建的
- 主队列与程序的
根据代码可知它的核心是调用了
DISPATCH_GLOBAL_OBJECT
函数,其中的有两个参数,第一个是类型,再来看看第二个参数_dispatch_main_q
,搜索得到:
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,
};
- 根据观察可发现队列的区分可能与
dq_atomic_flags
和dq_serialnum
两个参数有关,值分别为DQF_THREAD_BOUND | DQF_WIDTH(1)
和1
全局队列
- 再搜索
dispatch_get_global_queue
:
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
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;
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == QOS_CLASS_MAINTENANCE) {
qos = DISPATCH_QOS_BACKGROUND;
} else if (qos == QOS_CLASS_USER_INTERACTIVE) {
qos = DISPATCH_QOS_USER_INITIATED;
}
#endif
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
identifier
可以设置一些优先级
,有四种优先级:DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
flags
是留给将来使用,任务非0
值都可能导致NULL
,通常传0
这里返回的是
_dispatch_get_root_queue
函数的调用
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];
}
- 再继续查看
_dispatch_root_queues
函数:
此时找到了与dq_atomic_flags
中相关参数DQF_WIDTH
,传入的值为DISPATCH_QUEUE_WIDTH_POOL
,也就是DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 1)
,但不能确定dq_serialnum
,它的值跟label
有关,再来打印下_dispatch_root_queues
的label
:
globalQueue: <OS_dispatch_queue_global: com.apple.root.default-qos>
- 于是根据
label
在源码中确定dq_serialnum
为10
,目前还是不能确定那个队列的区分和哪个参数有关
自定义队列
- 再来搜索队列的创建
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_lane_create_with_target
:
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy) // tq NULL, legacy true
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa); // 面向对象封装
...
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; // 创建痕迹标识,方便查找
}
- 该函数第一个参数
label
我们比较熟悉,就是创建的线程的名字 _dispatch_queue_attr_to_info
传入的第二参数:
dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
dispatch_queue_attr_info_t dqai = { }; // 初始为NULL,
if (!dqa) return dqai;
...
}
- 这里先初始一个
dispatch_queue_attr_info_t
类型对象,然后在根据dpa
类型进行相关赋值,如果dqa
为不存在则直接返回,这就是串行可以传NULL
的原因 - 做好相关的准备工作后,接着在调用
_dispatch_object_alloc
方法对线程开辟内存 - 再调用初始化函数
_dispatch_queue_init
,此处第三个参数有判断是否并判断,如果是并发传入为DISPATCH_QUEUE_WIDTH_MAX
,串行则传入1
,继续查看方法的实现:
- 此处可以看出又出现了
DQF_WIDTH()
函数和dq_serialnum
,并发DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 2)
,串行为DQF_WIDTH(1)
,但根据队列类型传入的参数只和DQF_WIDTH
有关,那么dq_serialnum
是什么呢? - 搜索
_dispatch_queue_serial_numbers
:
unsigned long volatile _dispatch_queue_serial_numbers =
DISPATCH_QUEUE_SERIAL_NUMBER_INIT;
- 然后再搜索
DISPATCH_QUEUE_SERIAL_NUMBER_INIT
:
// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
- 根据注释得知
1代表 main_1
,4~15 代码 global queues
,但能区分队列吗,还得看看os_atomic_inc_orig
函数的实现:
#define os_atomic_inc_orig(p, m) \
os_atomic_add_orig((p), 1, m)
#define os_atomic_add_orig(p, v, m) \
os_atomic_c11_op_orig((p), (v), m, add, +)
#define _os_atomic_c11_op_orig(p, v, m, o, op) \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
memory_order_##m)
- 最终得到
C++
方法atomic_fetch_add_explicit
方法,网页搜索:
- 原来是原子相关的操作,没啥用
总结:
1. 串行队列:DQF_WIDTH(1)
2. 全局队列:DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 1)
3. 创建的并发队列:DQF_WIDTH(DISPATCH_QUEUE_WIDTH_FULL - 2)
队列的继承
在队列开辟内存时调用的是
_dispatch_object_alloc
方法,为什么不是_dispatch_dispatch_alloc
?接下来根据队列的类型进行分析下先搜索队列的类型
dispatch_queue_t
:
DISPATCH_DECL(dispatch_queue);
- 再查看
DISPATCH_DECL
的实现:
#define DISPATCH_DECL(name) \
typedef struct name##_s : public dispatch_object_s {} *name##_t
- 根据传入的参数
dispatch_queue
,得到:
struct dispatch_queue_s : public dispatch_object_s {} *dispatch_queue_t
- 于是得到队列的继承关系:
dispatch_queue_t
:dispatch_queue_s
:dispatch_object_s