简介
什么是GCD?
GCD
的全称是Grand Central Dispatch
,它是Apple
开发的一个多核编程的解决方法,由纯C
语言实现,提供了非常强大的函数,用来对多线程进行相关的操作。
GCD
的优势
-
GCD
会自动利用更多的CPU
内核(比如双核、四核) -
GCD
会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 程序员只需要告诉GCD
想要执行什么任务,不需要编写任何线程管理代码
任务和队列
GCD
当中,加入了两个比较重要的概念:任务(task)
和队列(queue)
。任务就是我们要执行的操作,而队列则表明了多个操作的执行方法。简而言之,GCD
的核心就是将任务添加到队列,并且指定函数执行任务。
任务
任务使用block
封装,该block
没有参数也没有返回值。
typedef void (^dispatch_block_t)(void);
dispatch_block_t block = ^{
};
执行任务有两种方式:同步和异步。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步执行
(sync)
:- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
dispatch_sync(, { ()-> Void in
})
- 异步执行
(async)
:- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
dispatch_async(, { ()-> Void in
})
所以同步任务会阻塞当前线程并等待block
中的任务执行完毕,才会执行下一个任务;而异步任务则不用等待当前语句执行完毕,就可以执行下一条语句,并不会出现前后任务互相阻塞等待的情况。
需要注意的是异步(async)
虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。
队列
队列:用于存放任务。GCD
中有两种队列:串行队列和并行队列。
- 串行队列
(Serial Dispatch Queue)
:
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务) - 并发队列
(Concurrent Dispatch Queue)
:
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
由于队列和任务时搭配使用,所以产生了下面四种方法:
-
- 同步函数串行队列:
- 不会开启线程,在当前线程执行任务
- 任务串行执行,任务一个接着一个
- 会产生堵塞
-
- 同步函数并发队列
- 不会开启线程,在当前线程执行任务
- 任务一个接着一个
-
- 异步函数串行队列
- 开启线程一条新线程
- 任务一个接着一个
-
- 异步函数并发队列
- 开启线程,在当前线程执行任务
- 任务异步执行,没有顺序,和
CPU
调度有关
GCD
的概念
GCD
的使用步骤很简单,首先创建一个队列(串行队列或并发队列),然后将任务追加到任务的等待队列中执行任务(同步执行或异步执行)。
队列的创建
#define DISPATCH_TARGET_QUEUE_DEFAULT NULL
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);
}
其参数如下:
-
const char *label
: 队列的唯一标识符,可以传空值。 -
dispatch_queue_attr_t attr
: 标识队列的类型,区分是串行队列还是并发队列。-
DISPATCH_QUEUE_SERIAL
: 串行队列 -
DISPATCH_QUEUE_CONCURRENT
: 并发队列
-
串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
常用的主队列就是串行队列,dispatch_get_main_queue()
- 专门用在主线程调度任务的队列,也称
UI
队列 - 不会再开启线程
- 如果有任务执行,再添加其他任务,会被堵塞
其实主队列其实并不特殊。只是默认情况下,如果没有开别的线程,程序都是放在主队列中的,而主队列又都会放到主线程中去执行,所以才造成了主队列特殊的现象。
并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
常用的全局队列就是并发队列dispatch_get_global_queue(long identifier, unsigned long flags)
,它可以直接执行异步任务。该方法第一个参数是优先级,全局队列的优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT
,这个值是一个为0的宏,所以也可以传0。unsigned long flags
: 苹果官方文档的解释是Flags that are reserved for future use
。标记这个参数是为了未来使用保留的,现在传0即可。
此处引入线程的优先级概念,优先级越高越先执行。
-
DISPATCH_QUEUE_PRIORITY_HIGH
: 2 -
DISPATCH_QUEUE_PRIORITY_DEFAULT
: 0 -
DISPATCH_QUEUE_PRIORITY_LOW
: (-2) -
DISPATCH_QUEUE_PRIORITY_BACKGROUND
:INT16_MIN
我们接着看看队列到底是如何创建的?
首先我们生成以下代码,并在控制台输出:
然后我们跟踪代码,去看看创建的过程。当我们创建一个并发或者串行队列的时候,最终会进入以下代码:
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.dqai_concurrent来判断
// dqai如果是空的结构体就是串行队列,如果有值就是并发队列
// 不管串行或者并发,tq都是DISPATCH_TARGET_QUEUE_DEFAULT == NULL
// 串行队列的 dqa == NULL,并发有值
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
// 串行队列的 dqai = {},并发有值
......
_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
......
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
// 给overcommit赋值
// 并发队列的overcommit为_dispatch_queue_attr_overcommit_disabled
// 串行队列的overcommit为_dispatch_queue_attr_overcommit_enabled
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
}
if (!tq) {
// 设置tq
// DISPATCH_QOS_UNSPECIFIED = 0 DISPATCH_QOS_DEFAULT = 4
// 创建的时候qos = 0
// _dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
// 第一个参数固定是4 第二个参数串行队列为true、并发队列为false
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 4
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; // 0 1
if (unlikely(!tq)) {
DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
}
}
// 开辟内存 - 生成响应的对象 queue
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;
// 优先级
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);
// 将tq的值赋给targetq
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
}
// 通过dqa获取到dqai
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;
// 给并发队列做相关的赋值操作
......
return dqai;
}
// 第一个参数固定是4 第二个参数串行队列为true、并发队列为false
DISPATCH_ALWAYS_INLINE DISPATCH_CONST
static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
// 4-1= 3
// 2*3+0/1 = 6/7
// 串行取到的是_dispatch_root_queues这个数组里面下标为7的元素
// 并发取到的是下标为6的元素
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
// 给队列赋值
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_state |= DISPATCH_QUEUE_INACTIVE + DISPATCH_QUEUE_NEEDS_ACTIVATION;
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 & DISPATCH_QUEUE_ROLE_MASK);
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);
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_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,
)
这个结果和打印的结果是完全相同的,这样也就走了一遍创建的过程。
同理,也可以根据打印的数据得到主队列的信息如下:
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,
};
看完了GCD
队列的相关信息,我们再来看看的队列和任务的配合使用。
GCD
的使用
GCD
的使用主要是任务和队列的配合使用,我们知道了任务的执行分为同步和异步,队列又有串行和并发之分。
1. 同步函数串行队列:
根据代码可以看出:
- 所有的任务默认在主线程执行
- 同步任务不具备开启新线程的能力,只能在当前线程执行任务
- 串行队列中的任务只能一个接着一个按顺序执行
- 如果存在耗时任务,会产生堵塞
由于主队列也是一种串行队列,我们再来看看同步函数搭配主队列会如何执行:
我们发现程序在第一个同步任务的地方崩溃了,这是因为此处发生了死锁。那是因为我们在主线程中执行syncTaskMainQueue
方法,即把syncTaskMainQueue
任务放到了主队列中。当我们把“打印1”这个同步任务追加到主队列中,“打印1”需要等待syncTaskMainQueue
的执行,而syncTaskMainQueue
需要等待“打印1”执行完毕,才能接着执行。这就形成了死锁。
那么我们在其他线程执行该syncTaskMainQueue
会死锁吗?调用如下方法:
[NSThread detachNewThreadSelector:@selector(syncTaskMainQueue) toTarget:self withObject:nil];
程序正常执行,因为此时syncTaskMainQueue
在其他线程执行,而我们的打印任务都在主线程执行。
2. 同步函数并发队列
- 所有的任务默认在主线程执行
- 虽然是并发任务,但是同步任务不具备开启新线程的能力,所以只能在当前线程执行任务
- 任务一个接一个按顺序执行
- 如果存在耗时任务,会产生堵塞
可以得出结论,无论是串行队列还是并发队列,只要是同步任务,都不会开启新线程,只能在当前线程执行任务,而且任务是一个接一个按顺序执行的,并且如果存在耗时任务会发生堵塞。
3. 异步函数串行队列
- 开启了一条新线程
- 任务一个接着一个,按顺序执行
4. 异步函数并发队列
- 开启其他线程执行任务
- 任务异步执行,没有顺序,和
CPU
调度有关
可以得出结论,执行异步任务的时候,如果是串行队列,只会开启一条新的线程,任务会在新线程中一个接一个按顺序执行,而且可能会发生堵塞;如果是并发队列,有多少任务就会创建多少新的线程,任务异步执行,和CPU
调度有关,没有特定顺序。
总结
GCD
的核心就是将任务添加到队列,并且指定函数执行任务。任务分为同步任务和异步任务,而队列又分为串行队列和并发队列。主队列dispatch_get_main_queue()
是常见的串行队列,全局队列dispatch_get_global_queue(0, 0)
是常见的并发队列。
任务和队列的配合使用分为同步函数串行队列、同步函数并发队列、异步函数串行队列、异步函数并发队列。
执行同步任务的时候,无论是串行队列还是并发队列,都不会开启新线程,只能在当前线程执行任务,而且任务是一个接一个按顺序执行的,并且如果存在耗时任务会发生堵塞。需要注意的是,在主队列中加入同步任务,可能会导致死锁。
执行异步任务的时候,如果是串行队列,只会开启一条新的线程,任务会在新线程中一个接一个按顺序执行,而且可能会发生堵塞;如果是并发队列,有多少任务就会创建多少新的线程,任务异步执行,和CPU
调度有关,没有特定顺序。