本章提纲:
1、GCD的介绍
2、函数
3、队列
4、队列与函数的组合使用
5、GCD部分源码解读
6、GCD部分习题
一、GCD的简介
GCD的全称是:Grand Central Dispatch
,是纯C语言实现的,是苹果公司为多核的并行运算提出的一套解决方案。
GCD优点:
- GCD会自动利用CPU内核。(比如双核,四核)
- 自动管理线程的生命周期。(包括线程创建、调度、销毁)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写线程管理相关的代码,使用方便。
二、函数
在GCD中,执行任务的有两种方式,分别是:同步函数
、异步函数
。
-
同步函数dispatch_sync
1、必须等待当前语句执行完毕,才会执行下一条语句;
2、不会开启新的线程;
3、在当前线程中执行block的任务。 -
异步函数dispatch_async
1、不会等待当前语句执行完毕,就可以执行下一句;
2、会开启线程执行block的任务,具备开启新线程的能力;
3、异步是多线程的代名词。
三、队列
队列分为串行队列
和并发队列
。队列是一种数据结构,遵循FIFO(Frist In Frist Out)先进先出的原则。后来的任务会排在队尾,先来的任务会被先执行。
-
串行队列
串行 -
并行队列
image.png
在GCD中,有两个常见的队列,主队列(main)
和全局并发队列(global)
。主队列属于串行队列,而全局队列是并发队列。
-
主队列
1、专门用来在主线程上调度任务的串行队列
2、不会开启线程
3、如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度。
4、可以通过dispatch_get_main_queue
来获得主队列。 -
全局并发队列
1、它是并发队列;
2、在使用多线程开发时,如果没有特殊的要求,执行异步任务时,可以直接使用全局队列;
3、使用dispatch_get_global_queue
可以获得全局并发队列。
全局队列和主队列的日常搭配使用示例:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//执行耗时操作,例如网络请求
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程进行UI操作,根据数据更新UI
});
});
四、队列与函数的组合使用
-
同步函数的串行队列
1、不会开启新的线程,在当前线程执行任务
2、任务串行的执行,一个接着一个的执行
3、会产生阻塞 -
同步函数的并发队列
1、不会开启新的线程,在当前线程执行任务
2、任务串行执行,一个接着一个的执行 -
异步函数的串行队列
1、开启一条新线程
2、任务串行执行,一个接着一个执行 -
异步函数的并发队列
1、开启一条新线程
2、任务异步执行,没有顺序,任务的顺序和CPU的调度有关
小结:
只有异步函数的并发队列的时候才会是真正的异步执行,没有顺序。
同步函数或者串行队列都会导致任务最终串行执行,一个挨着一个的执行。
五、GCD部分源码解读
阅读GCD的源码还是相对比较困难的,这份源码的注释比较少,宏定义比较多,纯c语言实现。
有了源代码,我们分别来了解一下我们常用的GCD函数底层实现。
- dispatch_get_main_queue源码分析
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
通过上面的dispatch_get_main_queue
的实现,我们可以看到,返回的是dispatch_queue_main_t
类型。通过方法DISPATCH_GLOBAL_OBJECT
来生成最后要返回的内容。而DISPATCH_GLOBAL_OBJECT
的实现如下:
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
可以通过上面的代码看到DISPATCH_GLOBAL_OBJECT
的最终结果是由type
和object
两个参数进行与操作的结果。
第一个参数传入的是dispatch_queue_main_t
,这个通过查询源码发现是一个结构体dispatch_queue_static_s
指针:
typedef struct dispatch_queue_static_s *dispatch_queue_main_t;
而dispatch_queue_static_s
的定义如下:
// Cache aligned type for static queues (main queue, manager)
struct dispatch_queue_static_s {
struct dispatch_lane_s _as_dl[0]; \
DISPATCH_LANE_CLASS_HEADER(lane);
} DISPATCH_CACHELINE_ALIGN;
根据这上面👆🏻的注释可以了解到,这是为了静态队列(主队列)服务的内存对齐类型。
这是传入的第一个参数。
第二个参数_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,
};
- DISPATCH_GLOBAL_OBJECT_HEADER传递的参数是queue_main
- dq_label貌似是队列名称
- DQF_WIDTH是用来区分串行队列还是并发队列的,DQF_WIDTH(1)表示串行,并不是通过serialnum来表示串行和并行的。
而且我们通过上面的源码关于dispatch_queue_static_s
部分,最终返回dispatch_queue_main_t
是通过dispatch_lane_s
来完成的。
(此部分比较困难,也是摸索着探究,很多地方也看不太懂,先整理一下初步的理解和思路,后续加深理解会更新)
- dispatch_get_global_queue源码分析
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);
}
- 方法
dispatch_get_global_queue
返回的类型是dispatch_queue_global_t
- 最终调用的方法是
_dispatch_get_root_queue
,而_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的实现如下:
// 6618342 Contact the team that owns the Instrument DTrace probe before
// renaming this symbol
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,
),
};
- _dispatch_root_queues[]是个数组之类的东西,根据下标不同去哪对应的队列。
- dq_serialnum这个貌似是表示不同类型的队列编号
以上这两种常见的队列源码的实现大致如上。
六、GCD习题解析
- 主队列同步
- (void)mainSyncTest{
NSLog(@"0");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
输出
:0,然后死锁
实际运行结果:
解析
:默认是在主队列上,然后同步函数不会额外开启新的线程,也是在主队列上,主队列执行完0,然后准备往下接着执行遇见了自己队列上的同步函数,主队列顺序执行此时应该执行同步函数,但是同步函数又再主队列上 dispatch_sync(dispatch_get_main_queue()
),又要接着等自己执行完毕,所以造成了死锁。也就是说主队列遇见同步函数没问题,但是同步函数又切回到了主队列上,主队列该执行同步函数了,然后同步函数切回主队列这样造成了死锁。
- 主队列异步
- (void)mainAsyncTest{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
输出
:2,1
解析
:都在主队列上,异步函数不会阻塞,主队列先遇到异步函数,然后是2,然后是异步函数切回主队列上的1。所以打印是2,1
- 全局队列同步
/**
全局同步
全局队列:一个并发队列
*/
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
输出
:0~19顺序输出、然后hello queue
解析
:主队列遇见同步任务,等待同步任务内部执行完毕,因为是同步任务所以等待块返回才会执行下一个,所以结果是先顺序输出,然后输出hello queue
- 全局队列异步
/**
全局异步
全局队列:一个并发队列
*/
- (void)globalAsyncTest{
for (int i = 0; i<20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
输出
:hello queue、然后乱序
解析
:都在默认的主队列上,然后先遇到异步函数,然后是hello queue
,所以先打印hello queue,然后异步函数执行内部的任务,是并发队列,异步任务,不用等待,所以后面乱序。
- 函数与队列练习
练习一
- (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,所以先打印1,5;然后异步块中的内容是2、异步块、4;所以再打印2、4,然后最后一个异步块中是3,再打印3。结果是1、5、2、4、3。
练习二
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
});
NSLog(@"5");
}
输出
:1、5、2,死锁
实际运行:
解析
:这个队列为NULL是默认是串行队列,主队列上首先是1、异步块、5,所以先打印1、5;然后异步块里面是一个串行队列上的一个2、同步块,2先打印,然后后面的同步块会阻塞队列后面的任务执行,同步块里的内容又等着同步函数执行完毕,同步函数等着队列上的3执行,又形成了死锁。
练习三
- (void)textDemo2{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
输出
:1、5、2、3、4;
解析
:和上面的分析差不多,区别在与同步函数阻塞了以后因为是并发队列,不用等着同步函数了,就先执行了3,然后再4这样。
- 微博面试题
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
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,这个因为都是异步任务,但是内部都是在串行队列,1,2,3分别按照顺序入队到串行队列queue中,3的地方是同步要等前面的执行完,所以3执行完是0,后面的7,8,9和前面的一样顺序打印。所以出来的是1,2,3,0,7,8,9。
- 美团面试题
- (void)MTDemo{
while (self.num < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num);
}
答案
:大于等于5。因为while是死循环 至少是5以后才会跳出循环,而异步并发的时候self.num不一定被加过几次,反正小于5之前就一直在循环。
- 快手面试题
- (void)KSDemo{
for (int i= 0; i<10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num); //
}
答案
:<=10000 ,异步并发队列执行到主队列上没顺序。
网友评论