一.什么是GCD?
1.GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式。
2.GCD是基于C语言的线程管理方案,使用者无需过多参与线程的管理,只需要将想要执行的代码,添加到想要添加的调度队列即可。
3.GCD主要用在后台执行较慢任务;延迟执行任务;以及在后台任务中,切换回主线程,更新UI。
二.GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案。
GCD会自动利用更多的CPU内核(比如双核、四核)。
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
三.任务和队列
3.1 队列
队列:用于存放要执行的任务。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在 GCD中有两种队列:
- 串行队列(Serial Dispatch Queue)
一个任务执行完毕后,再执行下一个任务
- 并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程执行任务)并发功能只有在异步(dispatch_async) 函数下才有效
DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因为宏定义定义的为NULL。
DISPATCH_QUEUE_CONCURRENT:表示并发
3.2 任务
任务:在线程中执行的操作,GCD中就是指Block中的代码。任务有两种方式:同步执行(sync)和异步执行(async)
。区别在于是否会创建新的线程。同步和异步的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕。
- 同步执行:当前任务不完成,不会执行下个任务。
- 异步执行:当前任务不完成,不会等待,同样可以执行下个任务。
四.GCD的串行队列,并发队列和全局队列
各种队列的执行效果:
4.1并发队列
1.并发队列,同步任务(不会开启新的线程,并发队列失去了并发的功能。)
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
//2.添加任务到队列中执行
for (int i=0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
总结:不会开启线程,并且会顺序执行。
2.并发队列,异步任务(执行较慢的任务,例如大量计算,网络请求等。)
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
//2.添加任务到队列中执行
for (int i=0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
总结:会开启线程,不会顺序执行。
4.2串行队列
1.串行队列,同步任务(在新线程中执行任务,并且等待线程执行完毕再向后执行,几乎不用)
//1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
//2.添加任务到队列中执行
for (int i=0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
2.串行队列,异步任务
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
NSLog(@"%d-----",i); //主线程,会打印完9之后,才会执行come here
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i); //子线程
});
}
NSLog(@"come here"); //在主线程,和队列没有任何关系
2021-03-23 23:35:59.401760+0800 多线程Demo[32679:1746588] 0-----
2021-03-23 23:35:59.401965+0800 多线程Demo[32679:1746588] 1-----
2021-03-23 23:35:59.402048+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 0
2021-03-23 23:35:59.402079+0800 多线程Demo[32679:1746588] 2-----
2021-03-23 23:35:59.402199+0800 多线程Demo[32679:1746588] 3-----
2021-03-23 23:35:59.402266+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 1
2021-03-23 23:35:59.402307+0800 多线程Demo[32679:1746588] 4-----
2021-03-23 23:35:59.402397+0800 多线程Demo[32679:1746588] 5-----
2021-03-23 23:35:59.402404+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 2
2021-03-23 23:35:59.402680+0800 多线程Demo[32679:1746588] 6-----
2021-03-23 23:35:59.402908+0800 多线程Demo[32679:1746588] 7-----
2021-03-23 23:35:59.403205+0800 多线程Demo[32679:1746588] 8-----
2021-03-23 23:35:59.403466+0800 多线程Demo[32679:1746588] 9-----
2021-03-23 23:35:59.403737+0800 多线程Demo[32679:1746588] come here
2021-03-23 23:35:59.404267+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 3
2021-03-23 23:35:59.404670+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 4
2021-03-23 23:35:59.405344+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 5
2021-03-23 23:35:59.405665+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 6
2021-03-23 23:35:59.406199+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 7
2021-03-23 23:35:59.407880+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 8
2021-03-23 23:35:59.408041+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 9
- 首先肯定是打印完9之后,才会打印come here,因为都是在主线程,然后打印数字和打印线程是交叉执行的。
总结:会开启新的线程,任务会顺序完成。
4.3主队列
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
- 总结:不会开启新线程,串行执行任务。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“在同步主线程中执行,慎用,否则会死锁”);
});
- 总结:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
五.GCD的其他用法以及注意事项
5.1异步主线程(用于在后台线程的任务将要完成时,切换到主线程更新UI)(不会开新线程)
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在异步主线程中执行");
});
// 执⾏耗时的异步操作...
dispatch_async( dispatch_get_global_queue(0, 0), ^{ //请求数据
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,执⾏UI刷新操作
//对图片或别的操作进行赋值等,回到主线程
});
});
5.2全局队列(相当于并发队列)
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
5.3延时执行线程
//延迟
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"我在五秒后打印");
});
5.4单例模式(只执行一次)
static GGT_Singleton *singleton = nil; //在.m中保留一个全局的static的实例
+ (GGT_Singleton *)sharedSingleton {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[GGT_Singleton alloc]init];
});
return singleton;
}
5.5队列组
如果有三个异步操作(例:网络请求ABC),想在三个请求全部执行完返回结果后,再做其他操作。
1、这三个网络请求,执行无序,并发执行。
2、不论返回失败还是成功都算返回结果。
如何处理?
如果满足上面的需求,需要使用GCD里面的dispatch_group_enter和dispatch_group_leave来实现。
dispatch_group_enter:通知 group,下个任务要放入 group 中执行了。
dispatch_group_leave:通知 group,任务成功完成,要移除,与enter()成对出现。
dispatch_group_notify:只要任务组完成才会调用,不完成不会调用。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
NSLog(@"1 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
NSLog(@"结束");
});
2022-02-11 18:18:51.877309+0800 TestDemo[14721:238852] 1 <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071275+0800 TestDemo[14721:238852] 2 <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071532+0800 TestDemo[14721:238763] 结束
5.6快速迭代(使用dispatch_apply函数能进行快速迭代遍历)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ //%zu用来输出size_t 类型
// 执行10次代码,index顺序不确定
NSLog(@"%zu",index);
});
5.7 iOS中的多读单写安全方案
//初始化队列
dispatch_queue_t queue = dispatch_queue_create("rw_queue",DISPATCH_QUEUE_CONCURRENT);
//读
dispatch_async(queue, ^{
});
//写
dispatch_barrier_async(queue, ^{
});
六.小结
- 同步任务死锁:当前是在主线程,让主队列执行同步任务!
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
//1.队列 --> 已启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任务 ==> 死锁
dispatch_sync(q, ^{
NSLog(@"能来吗? ");
});
NSLog(@"come here");
解决办法:
void (^task)() = ^{
NSLog(@"这里%@",[NSThread currentThread]);
//1.队列 --> 已启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任务
dispatch_sync(q, ^{
NSLog(@"能来吗? %@",[NSThread currentThread]);
});
NSLog(@"come here");
};
dispatch_async(dispatch_get_global_queue(0, 0), task);
竞争&同步:两个线程抢夺同一个资源,就会竞争,为了防止竞争,一个线程拥有资源的时候,会对资源加锁,另一个线程就要等待解锁以后再拥有这个资源,这叫同步。
死锁:两个线程互相等待对方释放资源。
主线程&后台线程:主线程也叫前台线程,程序启动的默认线程,操作UI的线程。后台线程,即非主线程,用于不影响主线程的完成一些任务。
同步&异步:同步执行线程,等待新线程执行完以后,再继续执行当前线程,很少用到。异步执行线程,在执行新线程的同时,继续执行当前线程,常用。
iOS 多线程入门01--概念知识
iOS 多线程入门02--NSThread
iOS 多线程入门04--NSOperation