一.什么是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的串行队列,并发队列和全局队列
组合使用的一般是:
1.串行队列,同步任务:不会开启新的线程,并且任务会顺序执行。
2.串行队列,异步任务:会开启新的线程,任务会顺序完成。
3.并发队列,同步任务:不会开启线程,并且会顺序执行。
4.并发队列,异步任务:会开启线程,不会顺序执行。
1和3的作用是一样的。
4.1串行队列
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.串行队列,异步任务
-
2.1
/*
会开线程吗? 会顺序执行吗? come here位置确定吗?
会 不一定 不确定
*/
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i); //子线程
});
}
NSLog(@"come here"); //在主线程,和队列没有任何关系
-
2.2
/*
会开线程吗? 会顺序执行吗? come here位置确定吗?
会 不一定 相对不确定
*/
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"); //在主线程,和队列没有任何关系
2018-09-01 14:15:51.719251+0800 GCD演示[6004:3053552] 0-----
2018-09-01 14:15:51.719581+0800 GCD演示[6004:3053552] 1-----
2018-09-01 14:15:51.719694+0800 GCD演示[6004:3053552] 2-----
2018-09-01 14:15:51.719791+0800 GCD演示[6004:3053552] 3-----
2018-09-01 14:15:51.719882+0800 GCD演示[6004:3053552] 4-----
2018-09-01 14:15:51.719970+0800 GCD演示[6004:3053552] 5-----
2018-09-01 14:15:51.720057+0800 GCD演示[6004:3053552] 6-----
2018-09-01 14:15:51.722579+0800 GCD演示[6004:3053552] 7-----
2018-09-01 14:15:51.722707+0800 GCD演示[6004:3053552] 8-----
2018-09-01 14:15:51.722798+0800 GCD演示[6004:3053552] 9-----
2018-09-01 14:15:51.723057+0800 GCD演示[6004:3053552] come here
2018-09-01 14:15:51.734849+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 0
2018-09-01 14:15:51.735336+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 1
2018-09-01 14:15:51.735606+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 2
2018-09-01 14:15:51.741201+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 3
2018-09-01 14:15:51.742350+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 4
2018-09-01 14:15:51.742660+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 5
2018-09-01 14:15:51.743100+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 6
2018-09-01 14:15:51.743355+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 7
2018-09-01 14:15:51.743598+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 8
2018-09-01 14:15:51.743836+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 9
- 首先肯定是打印完9之后,才会打印come here,因为都是在主线程,然后打印数字和打印线程是交叉执行的。
总结:会开启新的线程,任务会顺序完成。
4.2并发队列
1.并发队列,同步任务(不会开启新的线程,并发队列失去了并发的功能。)
/*
会开线程吗? 会顺序执行吗? come here位置确定吗?
不会 会 最后
*/
//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.并发队列,异步任务(执行较慢的任务,例如大量计算,网络请求等。)
/*
会开线程吗? 会顺序执行吗? come here位置确定吗?
会 不一定 come here 不确定
*/
//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.3全局队列(本质上是并发队列)
/*
会开线程吗? 会顺序执行吗? come here位置确定吗?
会 不一定 最前面
*/
//全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
for (int i = 0; i< 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
五.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_main_queue(), ^{
NSLog(@“在同步主线程中执行,慎用,否则会死锁”);
});
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队列组
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 执行1个耗时的异步操作
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 执行1个耗时的异步操作
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
NSLog(@"结束");
});
2018-09-01 18:17:15.119534+0800 003--GCD演示[6068:3101840] <NSThread: 0x11dd86970>{number = 5, name = (null)}
2018-09-01 18:17:15.120041+0800 003--GCD演示[6068:3101840] <NSThread: 0x11dd86970>{number = 5, name = (null)}
2018-09-01 18:17:15.120340+0800 003--GCD演示[6068:3101669] 结束
5.6快速迭代(使用dispatch_apply函数能进行快速迭代遍历)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ //%zu用来输出size_t 类型
// 执行10次代码,index顺序不确定
NSLog(@"%zu",index);
});
六.GCD的总结
主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
全局并行队列:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
手动创建串行队列:
//DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因为宏定义定义的为NULL。
dispatch_queue_t queue = dispatch_queue_create("a", NULL);
手动创建并行队列:
dispatch_queue_t queue = dispatch_queue_create("a", DISPATCH_QUEUE_CONCURRENT);
异步:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步(慎用):
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
需要注意的是:在MAC环境下,全局队列不需要释放,并发队列需要释放,方法是 dispatch_release(q)
七.小结
- 同步任务死锁:当前是在主线程,让主队列执行同步任务!
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
//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
网友评论