1. 什么是GCD?
全称是Grand Central Dispatch,是纯C语言,提供了非常多强大的函数。
2. GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案;
GCD会自动利用更多的CPU内核(比如双核、四核);
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码;
3. GCD中有2个核心概念
任务:执行什么操作;
队列:用来存放任务。
4. 队列的类型
并发队列(Concurrent Dispatch Queue)
a. 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务);
b. 并发功能只有在异步(dispatch_async)函数下才有效。
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。
主队列
特殊的串行队列,代表主线程。
5. GCD执行任务的2个函数
同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
(单词翻译:queue 是队列;block 是任务)
6. 同步和异步的区别
同步:在当前线程中执行。
异步:在另一条线程中执行。
7. 4个术语:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程。
同步:在当前线程中执行任务,不具备开启新线程的能力;
异步:在新的线程中执行任务,具备开启新线程的能力。
并发和串行决定了任务的执行方式。
并发:多个任务并发(同时)执行;
串行:一个任务执行完毕后,再执行下一个任务。
8. 串行队列
概念:
以先进先出的方式,顺序调度队列中的任务执行;无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务。
创建:
dispatch_queue_create(const char *label,dispatch_queue_attr_t attr);
参数1 Label: 队列名称
参数2 Attr: 队列的属性 (DISPATCH_QUEUE_SERIAL)
执行:
串行队列,同步执行,不开线程 (在当前线程执行)。
dispatch_queue_t q = dispatch_queue_create("test",
DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//同步执行
dispatch_sync(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
串行队列,异步执行,开一个线程,顺序执行。
只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test",
DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
9. 并发(并行)队列
概念:
以先进先出的方式,并发调度队列中的任务执行。
如果当前调度的任务是 同步执行 的,会等待任务执行完成后,再调度后续的任务;
如果当前调度的任务是 异步执行 的,同时底层线程池有可用的线程资源,会在新的线程调度后续任务的执行。
创建和执行:
并行队列,异步执行。
开多个线程,任务无序执行,效率最大,每次开启多少个线程是不固定的,线程数是由gcd来决定的,不由人为控制。
dispatch_queue_t q = dispatch_queue_create("test",
DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
并行队列,同步执行。
不开线程,顺序执行 ,与串行队列同步执行一样。
10. 主队列
主队列异步任务:不开线程,同步执行。
主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后再执行任务。
主队列又叫
全局串行队列。
//
得到主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
主队列同步执行:程序执行不出来(死锁)
主队列:如果主线程正在执行代码,就不调度任务。
同步执行:主线程下,往主队列添加任务并同步执行会造成死锁。
//造成死锁的代码
dispatch_sync
(mainQueue, ^{
NSLog
(
@"main queue %@"
,[
NSThread
currentThread
]);
});
11. 总结各队列的执行效果:
12. 线程间通信:从子线程回到主线程
示例:
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});
13. 同步执行
同步任务,可以让其他异步执行的任务,依赖某一个同步任务。
例如:在用户登录之后,再异步下载文件!
14. 全局并发(并行)队列
概念:
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建。
定义:
dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags);
//
参数
1:
优先级 long
identifier qos_class_t(iOS8);在
iOS8 之前参数名为
dispatch_queue_priority_t。
/**
iOS8 之后的优先级 qos_class_t(iOS8)
QOS_CLASS_USER_INTERACTIVE,用户交互(希望最快完成-不能用太耗时的操作)
QOS_CLASS_USER_INITIATED,用户期望(希望快,也不能太耗时)
QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
QOS_CLASS_UTILITY,实用工具(专门用来处理耗时操作!)
QOS_CLASS_BACKGROUND,后台
QOS_CLASS_UNSPECIFIED(0).未指定,可以和iOS 7.0 适配
iOS8 之前的优先级 dispatch_queue_priority_t
#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 后台
*/
//
参数
2:flags
没有用
,是保留参数,
给未来使用,
填
0
即可。
获取:
dispatch_queue_t
queue =
dispatch_get_global_queue
(0, 0);
15. 全局队列 & 并发队列的区别
全局队列:
a. 没有名称;
b. 无论 MRC & ARC 都不需要考虑释放;
c. 日常开发中,建议使用"全局队列”。
并发队列:
a. 有名字,和 NSThread 的 name 属性作用类似;
b. 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象;
c. dispatch_barrier 必须使用自定义的并发队列;
d. 开发第三方框架时,建议使用并发队列。
补充:
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
函数的作用:
如果任务是通过dispatch_barrier_async函数追加到concurrent queue中的,执行该任务时,其他的线程不执行,直到该任务完成,才恢复执行剩余的任务。
16. 延时执行
方法1:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:NO];
方法2:调用NSObject的方法
// 2秒后再调用self的run方法
[self performSelector:@selector(task) withObject:nil afterDelay:1];
方法3: 使用GCD函数
/**
参数
1:
延时的时间
dispatch_time
生成的时间,以
纳秒为计时单位,
精度高!
参数
2:
队列
参数
3:
任务
异步执行
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 1s后异步执行这里的代码。。。
});
17. 队列组
1. 问题的提出
有这么一种需求:
a:分别异步执行2个耗时的操作;
b:等2个异步操作都执行完毕后,再回到主线程执行操作。
2. 问题的实现
如果想要快速高效地实现上述需求,可以考虑用队列组。
//
创建组
dispatch_group_t
group =
dispatch_group_create
();
//
开启异步任务
//
参数
1:
组
//
参数
2:
队列
//
参数
3:
任务
dispatch_group_async
(group,
dispatch_get_global_queue
(0, 0), ^{
//
模拟网络卡
[
NSThread
sleepForTimeInterval
:
arc4random_uniform
(5)];
NSLog
(
@"LOL1.zip %@"
,[
NSThread
currentThread
]);
});
dispatch_group_async
(group,
dispatch_get_global_queue
(0, 0), ^{
//
模拟网络卡
[
NSThread
sleepForTimeInterval
:
arc4random_uniform
(5)];
NSLog
(
@"LOL2.zip %@"
,[
NSThread
currentThread
]);
});
//
参数
1:group
//
参数
2:
队列
//
参数
3:
任务
dispatch_group_notify
(group,
dispatch_get_main_queue
(), ^{
NSLog
(
@"
下载完成
%@"
,[
NSThread
currentThread
]);
});
18. 队列组扩展
a. 在终端中输入命令 man dispatch_group_async 查看队列组实现的原理。
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_retain
(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
block();
dispatch_group_leave(group);
dispatch_release
(group);
});
}
b. 重写17.队列组中的Demo如下:
dispatch_group_t
group =
dispatch_group_create
();
dispatch_group_enter
(group);
dispatch_async
(
dispatch_get_global_queue
(0, 0), ^{
//
模拟网络卡
[
NSThread
sleepForTimeInterval
:
arc4random_uniform
(5)];
NSLog
(
@"LOL1.zip %@"
,[
NSThread
currentThread
]);
dispatch_group_leave
(group);
});
dispatch_group_enter
(group);
dispatch_async
(
dispatch_get_global_queue
(0, 0), ^{
//
模拟网络卡
[
NSThread
sleepForTimeInterval
:
arc4random_uniform
(5)];
NSLog
(
@"LOL2.zip %@"
,[
NSThread
currentThread
]);
dispatch_group_leave
(group);
});
dispatch_group_notify
(group,
dispatch_get_main_queue
(), ^{
NSLog
(
@"
下载完成
%@"
,[
NSThread
currentThread
]);
});
19. 单例模式
作用:
可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源。
使用场合:
在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)。
核心代码:只执行一次,线程安全的。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要初始化的代码
});
20. 两种单例设计模式的比较
a. 创建一个类的两种单例实现方法:
// dispatch_once实现单例
+(instancetype)config{
static CZConfig *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[CZConfig alloc] init];
});
return instance;
}
// 同步锁实现单例
+(instancetype)configSync{
static CZConfig *instance;
@synchronized(self) {
if(instance == nil){
instance = [[CZConfig alloc] init];
}
}
return instance;
}
b. 性能测试与比较:
//dispatch_once 用时测试
//
CFAbsoluteTimeGetCurrent()主要用于调试函数的耗时,测试代码的效率
double
time1 =
CFAbsoluteTimeGetCurrent
();
for
(
int
i = 0; i < 999*999; i++) {
dispatch_async
(
dispatch_get_global_queue
(0, 0), ^{
CZConfig
*config = [
CZConfig
config
];
});
}
double
time2 =
CFAbsoluteTimeGetCurrent
();
NSLog
(
@"dispatch_once-----%f"
,time2-time1);
//
同步锁 用时测试
double
time3 =
CFAbsoluteTimeGetCurrent
();
for
(
int
i = 0; i < 999*999; i++) {
dispatch_async
(
dispatch_get_global_queue
(0, 0), ^{
CZConfig
*config = [
CZConfig
configSync
];
});
}
double
time4 =
CFAbsoluteTimeGetCurrent
();
NSLog
(
@"synchronized-----%f"
,time4-time3);
}
打印结果:
2015-10-16 23:00:48.468 16
单例模式
[14005:495101] dispatch_once-----1.231498
2015-10-16 23:00:52.428 16单例模式[14005:495101] synchronized-----3.959540
结论:
dispatch_once 的效率比同步锁 高很多。