GCD简介
GCD 全称 Grand Central Dispatch,基于C语言实现的多线程机制,将任务添加到队列,并指定执行任务的函数。
是Apple提供的一个多核编程的解决方案。它允许将一个程序切分为多个单一任务,然后提交到工作队列中并发或串行地执行。
GCD优势
- GCD 可用于
多核的并行运算
,会自动合理地利用CPU内核(比如双核、四核)。 - GCD 使用简单,开发者要做的只是定义执行的
任务
,追加到适当的队列
中,并且指定执行任务的函数
。配合Block,使用起来也更加方便灵活。 - GCD 会
自动管理线程的生命周期
(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
任务
任务就是要执行的操作,在GCD中也就是在Block内的代码段,任务的Block没有参数也没有返回值。
函数
函数决定了任务的执行方式。同步执行
还是 异步执行
。
两者的主要区别是:是否需要等待当前任务的返回结果,以及是否具备开启新线程的能力。
-
同步函数(sync)
- 同步函数执行的任务,调用一旦开始,调用者必须等待任务执行完毕,才能继续执行后续行为。
不具备开启新线程的能力
(任务只能在当前线程中执行任务)。
-
异步函数(async)
- 异步函数执行的任务,调用者无需等待任务执行完毕,就可以继续执行后续行为。
具备开启新线程的能力
(可以在新的线程中执行任务)。
异步执行虽然具有开启新线程的能力,但并不一定要开启新线程,还与任务所属队列有关,eg:异步执行主队列中的任务就不会开启新线程。
同步执行的任务记为同步任务 dispatch_sync 异步执行的任务记为异步任务 dispatch_async。
队列
是一种特殊的线性表,基本特性就是 FIFO(先进先出)
在GCD中,需要将任务添加到队列中,新任务被插入到队列的末尾,而调度任务的时候总是从队列的头部开始执行。每调度一个任务,则从队列中移除该任务。
在GCD中有两种队列:串行队列
和 并发队列
。两者都遵循FIFO原则,两者的主要区别是:执行顺序不同
,使用的线程个数不同
。
-
串行队列
每次只有一个任务被调度
,任务一个接一个地执行,且任务都在同一个线程执行
。只有前一个任务被调度执行完毕才能调度下一个任务 -
并发队列可以让
多个任务并发(”同时“)执行
。这取决于有多少可以利用的线程,假设在两条线程可用的情况下,那么任务1和任务2可分别在不同线程中并发执行
函数与队列
- 同步函数串行队列
- 不会开启线程,在当前线程执行任务
- 任务串行执行,任务一个接一个执行
- 会产生堵塞
// 同步+串行 任务
- (void)sync_serial{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
// 任务 1
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
- 同步函数并行队列
- 不会开启线程,在当前线程执行任务
- 任务一个接一个执行
// 同步+并发 任务
- (void)sync_concurrent{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
// 任务 1
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
任务的依赖的是
线程
而非队列,虽然并发队列支持多任务同时执行,但同步并不具备开启线程的能力,只能利用当前线程,当前线程只有一个(主线程),所以任务还是依次在主线程中执行
- 异步函数串行队列
- 开启一条新线程线程,
串行队列
的任务都在同一条线程执行 - 任务一个接一个执行
- 开启一条新线程线程,
// 异步+串行 任务
- (void)async_serial{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
// 任务 1
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
- 异步函数并行队列
- 可开启线程多个线程,同时执行多个任务
- 任务异步执行,没有顺序,CPU调度有关
// 异步+并发 任务
- (void)async_concurrent{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
// 任务 1
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
- 同步+主队列
```objc
// 同步+主队列 任务
- (void)sync_main{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 任务 1
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_sync(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
当运行至begin之后,程序崩溃退出了.
当前线程为主线程,在主线程中执行 sync_main
,相当于将sync_main
加入主队列中,当追加任务1的时候,再将任务1加入主队列,由于主队列是串行队列,任务1 就需等待sync_main
执行完毕,而又因是同步执行,sync_main
需等待任务1执行完毕 ,这样的互相等待,就导致了死锁的发生
-
异步+主队列(dispatch_get_main_queue())
-
主队列是专门用来在
主线程
上调度的串行队列
-
主队列不会开启线程,如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
-
所有的任务都是在主线程中执行的(虽然
异步执行
具备开启线程的能力,但因为是主队列,所以所有的任务都在主线程中)。 -
任务是按顺序执行(主队列是串行队列,每次只执行一个任务,任务一个接一个执行)
-
// 异步+主队列 任务
- (void)async_main{
// 打印当前线程
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"begin");
// 主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 任务 1
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 1---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 2
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 2---%@",[NSThread currentThread]); // 打印当前线程
});
// 任务 3
dispatch_async(queue, ^{
sleep(1); // 模拟耗时操作
NSLog(@"任务 3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end");
}
-
全局队列
- 为了方便使用,苹果提供了全局队列dispatch_get_global_queue(0,0),是一个并发队列
- 在使用多线程开发时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列。
并发队列的并发功能只有在 异步函数下才有效。
并发
与并行
的区别了,并行
是多核下真正的同时执行,而并发
则是由CPU时间片的轮转机制
【面试题】
- (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
}
- (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");
});
sleep(2);
NSLog(@"5");
// 12435
}
- (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
}
- (void)textDemo2{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
// NSLog(@"4");
});
NSLog(@"5");
// 1 5 2 死锁
}
GCD 底层原理
队列的创建
符号断点 dispatch_queue_create,定位底层库libdispatch.dylib
- dispatch_get_main_queue()解析:
搜索com.apple.main-thread
队列总共有两种: 并发队列
和 串行队列
- 串行队列:serialQueue、mainQueue
- 并发队列:concurrentQueue、globalQueue
初始化和下层的绑定 libdispatch_init
- dispatch_get_global_queue(0,0)解析
搜索
com.apple.root.default-qos
可查看到定义在struct dispatch_queue_global_s _dispatch_root_queues[]
中
#define _DISPATCH_GLOBAL_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__ \
}