iOS多线程开发之GCD
GCD简介
GCD
是Grand Central Dispatch
的缩写,是基于C语言。是苹果公司为多核的的并行运算提出的解决方案。GCD
负责创建线程和调度需要执行的任务,线程是由系统管理的。
开发中经常使用到GCD里的知识例如:
- 创建单例的时候:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//code
});
这个让code
只执行一次。
- 获取主线程
dispatch_async(dispatch_get_main_queue(), ^{
//code
});
- 异步执行
dispatch_async(dispatch_queue_create("robertlee", DISPATCH_QUEUE_SERIAL), ^{
//code
});
GCD
能做的事远不止这些,接下来让我们了解一下GCD
。GCD有两个核心概念,分别是任务和队列,接下来让我们介绍一下。
队列和任务
任务
任务也就是用户提交给队列的工作单元,也就是代码块,在GCD中需要是以block的形式存在。
队列
Dispatch Queue
(队列) 是用来存放任务的集合,负责管理开发者提交的任务。队列严格遵循FIFO
(先进先出)的原则,队列会维护和使用一个线程池来处理用户提交的任务,线程池来执行队列管理的任务。
队列主要分两种:
-
串行队列(Serial Dispatch Queue):
串行队列中的线程池中只有一个线程,每次只能执行一个任务,前一个任务执行完,才能够执行下一个任务。 -
并行队列(Concurrent Dispatch Queue):
并行队列的线程池提供了多个线程,可以多个任务同时执行。
队列的创建
//表示队列
dispatch_queue_t
//创建队列,第一个参数表示队列的字符串,第二个控制创建队列的类型,
//若第二个参数为NULL,则表示创建的队列为串行队列
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
- 创建串行队列
//创建串行队列:
dispatch_queue_t syncqueue = dispatch_queue_create("robertlee.testqueue", DISPATCH_QUEUE_SERIAL);
- 创建并行队列
//创建并行队列
dispatch_queue_t casyncqueue = dispatch_queue_create("robertleel.testqueue", DISPATCH_QUEUE_CONCURRENT);
- 获取全局并发队列
全局并发队列可以同时并行的执行多个任务,系统提供了多个并发的队列,整个应用内共享,可以使用dispatch_get_global_queue()
函数获取这些队列
dispatch_queue_global_t
//第一个参数用于指定队列的优先级, 第二个参数为以后做准备,填0即可。
dispatch_get_global_queue(long identifier, unsigned long flags);
//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
系统宏定义了几个优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 //高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 //中 默认
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) //底
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN //后台
- 获取主队列
GCD自带了一个串行队列,就是主队列,只要提交到主队列中的任务,都会在主线程中执行。通过dispatch_get_main_queue()
函数获得主队列
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
提交任务
上面已经介绍了队列的创建,接下来让我们看看如何提交任务。
- 同步提交任务
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_sync_f(dispatch_queue_t queue,
void *_Nullable context, dispatch_function_t work);
GCD
提供了两种方法进行同步提交任务,第一种方法是将代码块以同步的方式提交给queue队列。第一个参数是目标队列,第二个参数为代码块。
第二种方式是将函数以同步的方式提交给队列,还包括了content 上下文。
-
异步的方式提交任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async_f(dispatch_queue_t queue,void *_Nullable context, dispatch_function_t work);
同步异步的方法名只差一个a字母,一个以同步方式执行任务,一个以异步的方式执行任务,后者在开发中最常用。
大致用法如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_P 大专栏 iOS多线程开发之GCDRIORITY_DEFAULT, 0), ^(void) {
//code
dispatch_async(dispatch_get_main_queue(), ^(void) {
//code
});
});
先异步执行代码块,在执行完之后,可能会刷新页面、修改UI等步骤,需要到主线程中执行。
上面我们说了有3种创建队列的方式,又有两种队列执行方式,所以按照正常情况下会有6中组合方式。
- 1、同步(sync) + 并行 //没开启新线程 串行执行任务
- 2、同步(sync) + 串行 // 没开启新线程 串行执行任务
- 3、同步(sync) + 主队列 //导致死锁 引发程序奔溃
- 4、异步(async) + 并行 // 开启新线程、并行执行任务
- 5、异步(async) + 串行 // 开启新线程,串行执行任务
- 6、异步(async) + 主队列 // 没开启新线程 串行执行任务
总结:同步异步觉得是否开启新线程,并发串行决定任务的执行方式。
其他常用方法
单次执行任务 dispatch_once()
dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block);
刚开始已经提到了单次执行方法。第一个参数表示dispatch_once_t
类型的指针,第二个参数是代码块,在创建代理的时候经常用到。
多次重复执行任务 dispatch_apply()
在GCD
中可以多次执行相同代码,使用dispatch_apply()
函数,第一个参数表示次数,第二个参数表示执行队列,第三个参数表示代码块。
void dispatch_apply(size_t iterations,
dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
DISPATCH_NOESCAPE void (^block)(size_t));
延时方法 dispatch_after()
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);
延时方法,代码块在延时之后才开始执行:
- 第一个参数表示延时时间
- 第二个参数表示延迟完执行任务的队列
- 第三个参数表示代码块
GCD 的队列组:dispatch_group
有时候我们在请求页面数据的时候,可能需要请求2个接口或者多个,我们希望所有请求完之后再刷新界面,这个时候如何保证接口都请求完成呢,这就是GCD
队列组的用处了。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"0");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"4");
});
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
也可以使用dispatch_semaphore_t
和dispatch_group_wait
实现,但是我们后面会介绍这个方法。
GCD 信号量 dispatch_semaphore
信号量在使用线程同步的时候经常用到,dispatch_semaphore
有三种方法可以使用
dispatch_semaphore_create:初始化一个信号量
dispatch_semaphore_signal:发送一个信号,让信号总量加1
dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
在YYCache中如何使用dispatch_semaphore
来给线程加锁:
在YYDiskCache
中使用的线程锁为:定义了一个信号量:dispatch_semaphore_t _lock;
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)
这就是GCD,有很多未使用的功能都没有介绍,下次遇到再补充。