GCD 简介
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
优势:
- GCD 可用于多核的并行运算;
- GCD 会自动利用CPU内核(比如双核,四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- GCD 中无需像使用NSThread那样提供自动释放池。
相关概念:
进程:正在进行中的程序称为进程,负责程序运行的内存分配;每一个进程都有独立的虚拟内存空间。
线程:进程中独立的执行路径(控制单元),包含了很多任务(同步、异步);一个进程至少包含一条线程,即主线程。
多线程:仅多核CPU支持多线程,单核CPU即使开了多线程也不会有变化;过多的线程加大内存消耗,目前主线程、子线程开销都为512KB。
任务:执行的操作,即block中执行的代码片段。有两种执行方式:同步执行
和异步执行
。
-
同步执行(dispatch_sync):
- 只能在当前线程中执行任务,不具备开启新线程的能力;
- 同步添加任务到指定的队列中,上一个任务执行完毕再执行下一个任务,需要等待、协调运行。
-
异步执行(dispatch_async):
- 可以在新的线程中执行任务,具备开启新线程的能力;
- 异步添加任务到指定的队列中,无需等待,可以继续执行任务。
异步执行虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。
队列:用来存放任务,采用FIFO原则
(先进先出)。有两种队列:串行队列
和并发队列
。
-
串行队列(Serial Dispatch Queue):
- 按照顺序调度任务,前一个任务不执行完毕,队列不会继续调度任务。
-
并发队列(Concurrent Dispatch Queue):
- 只要有空闲线程,队列就会调度当前任务,交给线程执行。
并发队列的并发功能只有在异步函数(dispatch_async)下才有效。
GCD 使用步骤
- 创建一个队列;
- 创建任务并添加到队列中
GCD中队列自动调度任务,放到对应的线程中执行
。
队列的创建
- GCD 使用
dispatch_queue_create
来创建队列。
/*
第一个参数:队列的唯一标识符,用于 DEBUG,可为空
第二个参数:识别是串行队列【 DISPATCH_QUEUE_SERIAL 】还是并发队列【 DISPATCH_QUEUE_CONCURRENT 】
*/
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.id.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.id.testQueue", DISPATCH_QUEUE_CONCURRENT);
- 对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)。
// 放在主队列中的任务,都会放到主线程中执行,UI操作必须在此线程内
dispatch_queue_t queue = dispatch_get_main_queue();
- 对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)。
// 第一个参数:队列优先级 第二个参数:暂时无用,用0即可
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任务的创建
// 同步执行
dispatch_sync(queue, ^{
// to do something
});
// 异步执行
dispatch_async(queue, ^{
// to do something
});
GCD 队列和任务
两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),就有四种不同的组合方式。队列和任务的组合方式看->这里<-
GCD 线程间的通信
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程间的通讯。
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
DLog(@"任务 i=%d 线程:%@", i, [NSThread currentThread]);
}
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2];
DLog(@"UI操作 线程:%@", [NSThread currentThread]);
});
});
}

GCD 常用方法
一次性代码(只执行一次):dispatch_once
/**
* dispatch_once:保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下也可以保证线程安全。
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 仅执行1次的代码(这里面默认是线程安全的)
DLog(@"UI操作 线程:%@", [NSThread currentThread]);
});
}
延时执行方法:dispatch_after
/**
* dispatch_after:并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。
*/
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
DLog(@"UI操作 线程:%@", [NSThread currentThread]);
});
}
快速迭代方法:dispatch_apply
通常用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply
。
如果是在串行队列中使用 dispatch_apply
,那么就和 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列
进行异步执行
。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply
都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的dispatch_group_wait
方法。
/**
* dispatch_apply:按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
*/
- (void)apply {
DLog(@"线程:%@", [NSThread currentThread]);
DLog(@"---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(6, queue, ^(size_t index) {
DLog(@"index=%zd 线程:%@", index, [NSThread currentThread]);
});
DLog(@"---end");
}

栅栏方法:dispatch_barrier_async
当需要异步执行
两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样就需要一个栅栏方法将两组异步执行
的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async
方法在两个操作组间形成栅栏。
/**
* dispatch_barrier_async:函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。
* 当函数【dispatch_barrier_async】追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("com.queue.barrier", DISPATCH_QUEUE_CONCURRENT);
// 任务A
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
DLog(@"任务A i=%d 线程:%@", i, [NSThread currentThread]);
}
});
// 任务B
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
DLog(@"任务B i=%d 线程:%@", i, [NSThread currentThread]);
}
});
// 栅栏任务,如果传递给此函数的队列是串行队列或全局并发队列,则此函数的行为类似于dispatch_async函数
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
DLog(@"栅栏任务 i=%d 线程:%@", i, [NSThread currentThread]);
}
});
// 任务C
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
DLog(@"任务C i=%d 线程:%@", i, [NSThread currentThread]);
}
});
}

GCD通知组和信号量看->这里<-
相关文章:
参考资料
网友评论