1、字面理解
GCD
全称Grand Central Dispatch
,是苹果提供的一个多核编程的解决方案,在真正意义上实现了并行操作,而不是并发。
GCD
使用线程池模型来执行用户提交的任务,所以它比较节约资源,不需要为每个任务都重新创建一个新的线程,GCD
不需要自行编写并行代码,而是自动进行多核的并行计算,自动管理线程的生命周期,如:使用线程池管理线程的创建和销毁,线程的调度,任务的调度等,用户只需要编写任务代码并提交即可。
GCD
中有两个比较重要的概念:任务和队列
GCD
的任务
任务就是我们需要执行的的代码,它可以是一个方法也可以是一个block
,实际上就是我们需要线程为我们完成的工作。我们把任务提交给GCD
的队列,GCD
就可以自动完成任务的调度,线程的调度,也很方便的实现了多线程并发执行任务。GCD
的队列
队列是用于管理用户提交的任务,它有两种形式:串行队列和并行队列。
串行队列:GCD
底层只维护一个线程,任务只能串行依次执行。
并发队列:GCD
底层使用线程池维护多个线程,任务可并行或并发执行。
无论是并行队列还是串行队列,都是按先进先出FIFO
的方式来处理用户提交的任务。
在GCD
里,我们只需要创建队列或者获取系统队列,编写任务代码,并把任务提交给队列就可以了。
2、GCD
获取队列的方法
/*获取系统队列*/
/*
获取主队列,即与主线程相关联的队列
如果需要提交任务到主线程使用该方法获取主线程的主队列即可
主队列是串行队列因为只维护主线程一个线程
*/
dispatch_queue_t dispatch_get_main_queue(void)
/*
获取一个全局的并发队列
identifier指定该队列的优先级可选值有:
DISPATCH_QUEUE_PRIORITY_HIGH 2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
flags 是苹果预留参数,未来可能用的到,现在只传0就可以了
*/
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags)
/*用户创建队列*/
/*
创建一个队列
label 队列的名称
attr 队列的属性可选值有:
DISPATCH_QUEUE_SERIAL 创建一个串行队列
DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
通过这种方式可以自己维护一个队列
*/
dispatch_queue_t dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr)
3、队列获取示例
//串行主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并行队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//自建并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//自建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
4、 向队列提交任务的方法
/*
以异步方式执行任务,不阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
/*
同上
context 是一个void*的指针,作为work的第一个形参
work 是一个函数指针,指向返回值为void 形参为void*的函数,且形参不能为NULL,也就是说context一定要传
使用起来不方便,一般不怎么用,需要使用C函数,也可以使用OC方法通过传递IMP来执行但是会有编译警告
*/
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
/*
以同步方式执行任务,阻塞当前线程,必须等待任务完成当前线程才可继续执行
*/
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
//同上
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
/*
以同步方式提交任务,并重复执行iterations次
iterations 迭代执行次数
queue 管理任务的队列,任务最终交由该队列来执行
block block形式的任务,该block返回值为void形参为iterations迭代次数
*/
void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));
//同上
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void *_Nullable context, void (*work)(void *_Nullable, size_t));
/*
以异步方式提交任务,在when时间点提交任务
queue 管理任务的队列,任务最终交由该队列来执行
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
//同上
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
/*
以异步方式提交任务,会阻塞queue队列,但不阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
需要说明的是,即时使用并发队列,该队列也会被阻塞,前一个任务执行完成才能执行下一个任务
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//同上
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
/*
以同步方式提交任务,会阻塞queue队列,也会阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
同样的,即时是并发队列该队列也会被阻塞,需要等待前一个任务完成,同时线程也会阻塞
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
//同上
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
/*
底层线程池控制block任务在整个应用的生命周期内只执行一次
predicate 实际为long类型,用于判断是否执行过
block block形式的任务,该block返回值、形参都为void
该方法常用于实现单例类,以及结合RunLoop创建一个常驻内存的线程
*/
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
5、异步提交任务到并发队列
//自建并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//异步提交任务到并发队列
dispatch_async(concurrentQueue, ^{
NSInteger i = 0;
while (i < 50) {
i++;
NSLog(@"task1: %ld thread:%@",i,NSThread.currentThread);
}
});
dispatch_async(concurrentQueue, ^{
NSInteger i = 0;
while (i < 50) {
i++;
NSLog(@"task2: %ld thread:%@",i,NSThread.currentThread);
}
});
NSInteger indexTag = 10;
//调用C函数
dispatch_async_f(concurrentQueue, &indexTag, cFunctionDispatch);
/*
//oc转换函数
dispatch_async_f(concurrentQueue,&indexTag,[self methodForSelector:@selector(ocFunctionDispatch:)]);
*/
从控制台可以看到异步执行提交任务到并行队列,任务是分配到不同的线程的,并发执行。
6、异步提交任务到串行队列
//异步提交任务到串行队列 任务是在同一个线程执行,前一个任务执行完之后,后一个任务才会执行。
dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task5: %ld thread:%@",i,NSThread.currentThread);
}
});
dispatch_async(serialQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task6: %ld thread:%@",i,NSThread.currentThread);
}
});
从控制台打印的数据可以看到,串行队列的任务是分配到同一个线程执行,遵循FIFO
规则。前一个任务完成之后,后一个任务才会被调度执行。
7、同步提交任务到并发队列
//同步提交任务掉并发队列
dispatch_sync(concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task7: %ld thread:%@",i,NSThread.currentThread);
}
});
dispatch_sync(concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task8: %ld thread:%@",i,NSThread.currentThread);
}
});
dispatch_sync(concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task9: %ld thread:%@",i,NSThread.currentThread);
}
});
dispatch_async(concurrentQueue, ^{
dispatch_sync(concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task10: %ld thread:%@",i,NSThread.currentThread);
}
});
});
从输出可以看出,前三个任务都是用主线程来执行,最后一个在number=6的线程里执行的,按照并发队列的特性,这里的三个任务完全可能由不同的三个线程来执行。经测试,同步执行任务阻塞线程,当前队列在哪个线程里,当前任务就会被调度到哪个线程执行。并且任务的执行先后顺序是FIFO
原则。
8、同步提交任务到串行队列
这个就不测试了。同步执行阻塞当前线程,同时串行执行,由当前线程执行任务。都是先进先出的规则FIFO
。
8、dispatch_barrier _ (a)sync
- (void)viewWillAppear:(BOOL)animated
{
dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task0 %@ %d", [NSThread currentThread], i);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
});
dispatch_barrier_async(concurrentQueue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 500; i++)
{
NSLog(@"Task4 %@ %d", [NSThread currentThread], i);
}
});
}
上面的输出是按照Task0 Task1
并发执行,Task2
等待Task0 Task1
执行完成后单独执行, 最后Task3 Task4
等待Task2
执行完成后开始并发执行 。
这里需要讲解一下阻塞队列的概念,前文讲过不论是并发队列还是串行队列都是使用FIFO
先进先出的方式管理的,队列会从队首获取要执行的任务并交由对应线程处理,串行队列只有一个线程所以是顺序执行,并发队列有多个线程,但获取任务依旧是FIFO按顺序获取,只是执行时有多个线程。阻塞线程即,获取一个任务后,这个任务必须要执行完成才能获取下一个任务,所以不管是并发还是串行队列,都得等前一个任务完成了才能从队列中获取下一个任务,这样就不难理解输出结果了,上述栗子改成串行队列结果也是一样的,如果使用同步提交效果也是一样的,读者可以自行尝试,篇幅问题不再赘述了。
dispatch_barrier_async
方法常与并发队列共用,前一段任务使用dispatch_async
异步并发执行,然后插入一个dispatch_barrier_async
执行一个中间任务,这个中间任务必须要等待前面的并发任务执行完成后才能开始执行,接着这个中间任务完成后,继续异步并发执行接下来的任务。
9、dispatch_once
该方法能够保证在应用的生命周期内只执行一次提交的任务,所以常用于单例类的创建,举个单例类的栗子如下:
@interface MyUtil: NSObject <NSCopying>
+ (instancetype)sharedUtil;
@end
@implementation MyUtil
static MyUtil *staticMyUtil = nil;
+ (instancetype)sharedUtil
{
//保证初始化创建只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
staticMyUtil = [[MyUtil alloc] init];
});
return staticMyUtil;
}
//防止通过alloc或new直接创建对象
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
//保证alloc函数只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
staticMyUtil = [super allocWithZone:zone];
});
return staticMyUtil;
}
//实现NSCopying协议的方法,防止通过copy获取副本对象
- (instancetype)copyWithZone:(NSZone *)zone
{
return staticMyUtil;
}
@end
dispatch_once
函数需要传入一个long
类型的predicate
,这个值必须是独一无二的,使用静态变量的地址最合适不过了,MyUtil实现了NSCopying
协议的copyWithZone:
方法,防止通过copy方法获取副本对象。
当使用alloc&&init
方法初始化时,先调用allocWithZone:
方法来分配存储空间,如果再次使用sharedUtil
方法来获取的话,由于没有执行过,会执行到dispatch_once
内部block
,此时会再去执行allocWithZon
e:方法,但该方法内部dispatch_once
已经执行过了会直接返回staticMyUtil
,反过来调用是一样的道理,通过这样的方式就可以实现真正的单例了。
10、dispatch_group_t
dispatch_group_t是一个比较实用的方法,通过构造一个组的形式,将各个同步或异步提交任务都加入到同一个组中,当所有任务都完成后会收到通知,用于进一步处理,通过这样的方式就可以实现多线程下载,当下载完成后就可以通知用户了,举个简单的栗子如下:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);//enter和leave group可以保证三个任务都执行完之后再调用dispatch_group_notify的回调
dispatch_group_async(group, concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task11: %ld thread:%@",i,NSThread.currentThread);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_main_queue(), ^{
dispatch_async(concurrentQueue, ^{
sleep(2);
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task12: %ld thread:%@",i,NSThread.currentThread);
}
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, concurrentQueue, ^{
NSInteger i = 0;
while (i < 10) {
i++;
NSLog(@"task13: %ld thread:%@",i,NSThread.currentThread);
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, concurrentQueue, ^{
NSLog(@"group task completed!");
});
三个异步任务完成之后,发出通知调用block
,输出:group task completed!
。
参考: