iOS-多线程(二)-GCD基础

多线程(一)-原理
多线程(二)-GCD基础
多线程(三)-GCD函数
多线程(四)-GCD定时器程序员

简介

什么是GCD?

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

    1. 同步函数串行队列:
    • 不会开启线程,在当前线程执行任务
    • 任务串行执行,任务一个接着一个
    • 会产生堵塞
    1. 同步函数并发队列
    • 不会开启线程,在当前线程执行任务
    • 任务一个接着一个
    1. 异步函数串行队列
    • 开启线程一条新线程
    • 任务一个接着一个
    1. 异步函数并发队列
    • 开启线程,在当前线程执行任务
    • 任务异步执行,没有顺序,和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: 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调度有关,没有特定顺序。

相关文章
相关标签/搜索