详细用法可以参考:GCD总结
一、GCD概念
1、什么是GCD
GCD全称是GrandCentral Central Dispatch(dispatch的意思是分发调度的意思),是用纯C语言,并且提供了非常多的强大的函数
GCD优势:
- 是苹果公司为多核的并行运算提出的解决方案
- 会自动利用更多的CPU内核,例如双核、四核
- 会自动管理线程的生命周期,例如创建线程、调度任务、销毁线程、程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程代码
2、任务和队列
任务是使用block封装的,并且任务的block没有任何参数也没有返回值,执行任务的函数有了两个函数,童虎函数和异步函数
a、同步函数和异步函数
- 同步函数:dispatch_sync,必须等待当前语句执行完毕,才会执行下一条语句,并且不会开启线程
- 异步函数:dispatch_async,异步其实是多线程的代名词,会开启线程执行block的任务,不用等待当前语句执行完毕,就可以执行下一条语句
b、串行队列和并发队列
这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
- 串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
c、队列和任务的使用
- 同步函数和串行队列:不会开启线程,在当前线程中执行任务,任务一个接一个的串行执行,会产生堵塞
- 同步函数和并发队列:不会开启线程,在当前线程中执行任务,任务一个接一个的串行执行,会产生堵塞
- 异步函数和串行队列:开启一个新线程,在新的线程中任务一个接一个执行
- 异步函数和并发队列:开启线程,任务在新的线程中执行
苹果系统提供了全局的同步队列和全局的异步队列
-
主队列:dispatch_get_main_queue()
- 专门用来在主线程上调度任务的队列
- 不会开启新线程
- 如果当前主线程上有正在执行的任务,那么无论主队列中当前被添加了什么任务,都不会被调度
-
全局队列:dispatch_get_global_queue(0,0)
- 为了方便程序员的使用,苹果提供了全局队列
- 是一个并发队列
- 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以使用全局队列,因为系统也会用到该并发队列,所以不能再该队列上使用栅栏任务
3、死锁
- 主线程因为同步函数的原因等着先执行任务
- 并且主队列等着主线程的任务执行完毕后再执行自己的任务,
- 这样主线程就和主队列因为互相等待造成死锁
二、面试题
1、面试题一:
下面代码的打印是什么
- (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");
}
答案是:15243
解释:
- 因为任务一是异步的,并且任务一和任务二是加到自己创建的队列中的,跟目前执行的方法不再同一队列,5不会瞪大执行到2再执行,所以1过完5,5过完才是2
- 2执行完了有把任务二加入到队列中,任务二是异步任务,所以4不会等到3执行后再执行
- 所以最后打印是15243
2、面试题二
下面代码的输出是下面ABCD哪一项 // A: 1230789 // B: 1237890 // C: 3120798 // D: 2137890
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// sleep(2);
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");
});
}
答案:AC
解释:
具体来说应该是3在0前面,0在789前面,12789几个的顺序不确定,我们把打印的数字当做任务几来解释哈!
- 任务1、2加入到并发队列中就开辟线程执行去了,因为不知道任务的执行时间,所以无法判断1、2的打印时机,
- 任务3通过同步的方式加入到队列中,就会在当前线程中直接执行,所以3在0前面
- 0打印完后才把任务7、8、9异步方式加到并发队列中,所以7、8、9在0后面,并且打印顺序不确定
3、面试题三
关于串行队列的面试题,下面打印输出是啥
- (void)textDemo2{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
// 异步函数-任务一
dispatch_async(queue, ^{
NSLog(@"2");
// 同步函数-任务二
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案:152---崩了
解释:
- 1打印完后会将任务1通过异步方式加到同步队列中,因为此时queue没有任何任务,所以系统会直接开辟一个线程去执行任务一
- 然后主线程往下执行,打印5,但是假如任务一执行够快,2可能在5前面执行,但是此题中因为打印2之前需要创建线程去执行任务一,所以5肯定在2前面打印
- 执行任务一时候,打印完2后又将同步任务加到同步队列中,因为是同步任务,所以会阻塞当前线程,任务一需要将任务二执行完才能往下走,但是同步队列中任务一在任务二前面,队列遵循先进先出原则,所以任务二需要等到任务一执行完了,才能执行。这样就形成了经典的死锁
注意:
因为死锁是因为线程和队列互相等到导致的,所以破除的方法有两个:
- 在别的线程中给同步队列添加同步任务
- 在别的队列中给同步队列添加同步任务
因为开线程只能在别的队列中开辟,所有,解决方法就是在别的队列中给同步队列添加同步任务
4、面试题4
下面打印是多少:
void testMethod(){
sleep(3);
}
- (void)wbinterDemo2{
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("com.lgcooci.cn", DISPATCH_QUEUE_SERIAL);
//任务一
dispatch_async(queue, ^{
testMethod();
});
//任务二
dispatch_sync(queue, ^{
testMethod();
});
//任务三
testMethod();
NSLog(@"%f",CFAbsoluteTimeGetCurrent()-time);
}
答案:9秒多一点点
解释:
- 因为是同步队列,并且当前在主线程中执行的,所以任务一会在主线程中执行,会阻塞主线程3秒
- 任务二是通过同步方式往同步队列中添加任务,但是因为目前执行的任务是在主队列中,而添加的同步队列是自己创建的,所以不会造成死锁
- 因为是同步队列,所以任务二会在当前主线程中执行,造成堵塞
- 所以打印的是9秒多一点,多一点那时间是执行任务的时间
5、面试题五
下面打印顺序:
- (void)wbinterDemo3{
dispatch_queue_t queue1 = dispatch_queue_create("com.lgcooci2.cn", DISPATCH_QUEUE_CONCURRENT);
//任务一
dispatch_async(queue1, ^{
NSLog(@"1");
});
//任务二
dispatch_async(queue1, ^{
NSLog(@"2");
});
//任务三
dispatch_barrier_async(queue1, ^{
NSLog(@"3");
});
//任务四
dispatch_async(queue1, ^{
NSLog(@"4");
});
NSLog(@"5");
}
答案:3在1、2后面,在4前面,5的位置不定
解释:
- 任务一盒任务二通过异步方式加入到并发队列中,所以他们的执行完的先后顺序不确定
- 任务3是以异步栅栏形式加入到并发队列中的,栅栏函数的作用就是堵塞队列,,让栅栏任务前的所有任务都执行完,然后再执行栅栏任务,栅栏任务执行完成后再执行后面的任务
- 但是栅栏任务不堵塞线程,代码会继续往下执行,但是打印5的效率不确定是否比打印其他的快,所以不确定5在哪个位置
三、GCD源码解析
1、dispatch_queue_create
我们先通过汇编形式来找出dispatch_queue_create是在哪个库,然后去对应库找到相应的源码
运行,我们进入到
然后我们在dispatch_queue_create这个地方加个断点,然后通过control+in进入到该函数汇编流程
但是我们还是没找到dispatch_queue_create对应的库
这个时候我们就需要用符号断点来查找了,我们加一个dispatch_queue_create的符号断点
这个时候我们就会发现dispatch_queue_create就是在libdispatch.dylib库里面
我们从苹果官网上下载libdispatch的源码,在里面直接搜dispatch_queue_cteate的定义因为这样搜效率更快
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
然后找到_dispatch_lane_create_with_target函数
我们创建队列分串行队列和并发队列,而这两种都是通过参数dispatch_queue_attr_t来区分创建的,并且我们发现在_dispatch_lane_create_with_target函数里面只有第一行用到dispatch_queue_attr_t参数的
所以我们具体分析下第一行代码,进入到_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 = { };
if (!dqa) return dqai;
#if DISPATCH_VARIANT_STATIC
if (dqa == &_dispatch_queue_attr_concurrent) {
dqai.dqai_concurrent = true;
return dqai;
}
#endif
if (dqa < _dispatch_queue_attrs ||
dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
}
// 苹果的算法
size_t idx = (size_t)(dqa - _dispatch_queue_attrs);
// 位域赋值
// 0000 000000000 00000000000 0000 000 1
dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;
//表示并发数
dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;
dqai.dqai_relpri = -(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;
dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;
dqai.dqai_autorelease_frequency =
idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
return dqai;
}
我们再看看dispatch_queue_attr_info_t结构
typedef struct dispatch_queue_attr_info_s {
dispatch_qos_t dqai_qos : 8;
int dqai_relpri : 8;
uint16_t dqai_overcommit:2;
uint16_t dqai_autorelease_frequency:2;
uint16_t dqai_concurrent:1;
uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
我们发现:
- 当dqa为空时候会返回一个空的结构体dqai,而创建串行队列时候参数dqa是NULL,所以我们知道_dispatch_queue_attr_to_info这个函数的作用是对并发队列的一个属性赋值的左右
- dispatch_queue_attr_info_t是个结构体,对于这个结构体我们想到的是通过位域来赋值
然后我们找到创建队列的代码
因为构造方法是_dispatch_object_alloc,我们看下_dispatch_object是个什么样的东西,通过搜索发现
/*!
* @typedef dispatch_object_t
*
* @abstract
* Abstract base type for all dispatch objects.
* The details of the type definition are language-specific.
*
* @discussion
* Dispatch objects are reference counted via calls to dispatch_retain() and
* dispatch_release().
*/
说明dispatch_object_t是一个抽象的类 然后我们找到
/*
* Dispatch objects are NOT C++ objects. Nevertheless, we can at least keep C++
* aware of type compatibility.
*/
typedef struct dispatch_object_s {
private:
dispatch_object_s();
~dispatch_object_s();
dispatch_object_s(const dispatch_object_s &);
void operator=(const dispatch_object_s &);
} *dispatch_object_t;
typedef union {
struct _os_object_s *_os_obj;
struct dispatch_object_s *_do;
struct dispatch_queue_s *_dq;
struct dispatch_queue_attr_s *_dqa;
struct dispatch_group_s *_dg;
struct dispatch_source_s *_ds;
struct dispatch_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
struct dispatch_semaphore_s *_dsema;
struct dispatch_data_s *_ddata;
struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
我们发现dispatch_object_t是一个联合体,可以很简单的保存各种值,因为互斥的原因,这样写可以简单的实现多态的特性,而且可以限制这个结构的多方面变化,并且这样写节省很多内存
我们先看一下队列的两种创建方式
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
//并发队列
dispatch_queue_t queue1 = dispatch_queue_create("cooci1", DISPATCH_QUEUE_SERIAL);
因为NSLog只能打印队列的内存地址,而不能打印队列的结构,所以我们通过lldb打印出两种队列的结构体数据,
再往下走我们看到初始化函数_dispatch_queue_init
// 构造方法
/*
在方法_dispatch_queue_attr_to_info里面,
如果是串行队列dqai.dqai_concurrent是空
如果是并发队列dqai.dqai_concurrent有值
*/
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
看一下参数DISPATCH_QUEUE_WIDTH_MAX
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
对应上面队列的结构,
- 同步队列:width = 0x1
- 并发队列:width = 0xffe 看一下参数DISPATCH_QUEUE_WIDTH_MAX = 0x1000 - 2 = 0xffe,这个参数就是最大并发数,为啥减2呢?
- 并发数不能写满,要留点预留
- 减1是给dispatch_queue_global_s用的
我们再看一下队列里面target的值
在创建时候 dq->do_targetq = tq;我们找一下tq
if (!tq) {
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");
}
}
因为
#define DISPATCH_QOS_UNSPECIFIED ((dispatch_qos_t)0)
#define DISPATCH_QOS_DEFAULT ((dispatch_qos_t)4)
所以qos = 4,串行队列overcommit等于0,并发队列overcommit等于1
我们看一下_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");
}
// 4-1= 3
// 2*3+0/1 = 6/7
return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}
因为qos = 4,overcommit等于0或者1
所以返回的是_dispatch_root_queues数组里面的第六或者第七个元素,我们再找下_dispatch_root_queues
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,
),
};
这就是为啥串行队列是com.apple.root.utility-qos.overcommit,并发队列是com.apple.root.default-qos
_dispatch_root_queues是dispatch_init时候创建的