GCD
的全称是Grand Central Dispatch
,它是Apple
开发的一个多核编程的解决方法,由纯C
语言实现,提供了很是强大的函数,用来对多线程进行相关的操做。编程
GCD
的优点GCD
会自动利用更多的CPU
内核(好比双核、四核)GCD
会自动管理线程的生命周期(建立线程、调度任务、销毁线程) 程序员只须要告诉GCD
想要执行什么任务,不须要编写任何线程管理代码GCD
当中,加入了两个比较重要的概念:任务(task)
和队列(queue)
。任务就是咱们要执行的操做,而队列则代表了多个操做的执行方法。简而言之,GCD
的核心就是将任务添加到队列,而且指定函数执行任务。数组
任务使用block
封装,该block
没有参数也没有返回值。bash
typedef void (^dispatch_block_t)(void);
dispatch_block_t block = ^{
};
复制代码
执行任务有两种方式:同步和异步。二者的主要区别是:是否等待队列的任务执行结束,以及是否具有开启新线程的能力。多线程
(sync)
:
dispatch_sync(, { ()-> Void in
})
复制代码
(async)
:
dispatch_async(, { ()-> Void in
})
复制代码
因此同步任务会阻塞当前线程并等待block
中的任务执行完毕,才会执行下一个任务;而异步任务则不用等待当前语句执行完毕,就能够执行下一条语句,并不会出现先后任务互相阻塞等待的状况。并发
须要注意的是异步(async)
虽然具备开启新线程的能力,可是并不必定开启新线程。这跟任务所指定的队列类型有关。app
队列:用于存听任务。GCD
中有两种队列:串行队列和并行队列。异步
(Serial Dispatch Queue)
: 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)(Concurrent Dispatch Queue)
: 可让多个任务并发(同时)执行。(能够开启多个线程,而且同时执行任务)因为队列和任务时搭配使用,因此产生了下面四种方法:async
CPU
调度有关GCD
的概念GCD
的使用步骤很简单,首先建立一个队列(串行队列或并发队列),而后将任务追加到任务的等待队列中执行任务(同步执行或异步执行)。ide
#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
: 2DISPATCH_QUEUE_PRIORITY_DEFAULT
: 0DISPATCH_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
的使用主要是任务和队列的配合使用,咱们知道了任务的执行分为同步和异步,队列又有串行和并发之分。
根据代码能够看出:
因为主队列也是一种串行队列,咱们再来看看同步函数搭配主队列会如何执行:
咱们发现程序在第一个同步任务的地方崩溃了,这是由于此处发生了死锁。那是由于咱们在主线程中执行syncTaskMainQueue
方法,即把syncTaskMainQueue
任务放到了主队列中。当咱们把“打印1”这个同步任务追加到主队列中,“打印1”须要等待syncTaskMainQueue
的执行,而syncTaskMainQueue
须要等待“打印1”执行完毕,才能接着执行。这就造成了死锁。
那么咱们在其余线程执行该syncTaskMainQueue
会死锁吗?调用以下方法:
[NSThread detachNewThreadSelector:@selector(syncTaskMainQueue) toTarget:self withObject:nil];
复制代码
程序正常执行,由于此时syncTaskMainQueue
在其余线程执行,而咱们的打印任务都在主线程执行。
能够得出结论,不管是串行队列仍是并发队列,只要是同步任务,都不会开启新线程,只能在当前线程执行任务,并且任务是一个接一个按顺序执行的,而且若是存在耗时任务会发生堵塞。
CPU
调度有关能够得出结论,执行异步任务的时候,若是是串行队列,只会开启一条新的线程,任务会在新线程中一个接一个按顺序执行,并且可能会发生堵塞;若是是并发队列,有多少任务就会建立多少新的线程,任务异步执行,和CPU
调度有关,没有特定顺序。
GCD
的核心就是将任务添加到队列,而且指定函数执行任务。任务分为同步任务和异步任务,而队列又分为串行队列和并发队列。主队列dispatch_get_main_queue()
是常见的串行队列,全局队列dispatch_get_global_queue(0, 0)
是常见的并发队列。
任务和队列的配合使用分为同步函数串行队列、同步函数并发队列、异步函数串行队列、异步函数并发队列。
执行同步任务的时候,不管是串行队列仍是并发队列,都不会开启新线程,只能在当前线程执行任务,并且任务是一个接一个按顺序执行的,而且若是存在耗时任务会发生堵塞。须要注意的是,在主队列中加入同步任务,可能会致使死锁。
执行异步任务的时候,若是是串行队列,只会开启一条新的线程,任务会在新线程中一个接一个按顺序执行,并且可能会发生堵塞;若是是并发队列,有多少任务就会建立多少新的线程,任务异步执行,和CPU
调度有关,没有特定顺序。