一、基础概念
1、GCD简介
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术。以优化应用程序支持多核心处理器和其他的对称多处理系统的系统。
•GCD属于函数级的多线程,性能更高,功能也更加强大。
•它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。
2、GCD核心概念
任务:具有一定功能的代码段。一般是一个block或者函数。
•分发队列:GCD以队列的方式进行工作,例如FIFO。
•GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务。
3、GCD中两种队列
dispatch queue分为下面2种:
•并发队列(ConcurrentQueue):一次只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。SerialQueue能实现线程同步
•串行队列(SerialQueue):可以并发地执行多个任务,但是遵守FIFO
4、GCD的功能:
dispatch_async() 往队列中添加任务,任务会排队执行
dispatch_after() 往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
dispatch_apply() 往队列中添加任务,任务会重复执行n次
dispatch_group_async() 将任务添加到队列中,并添加分组标记
dispatch_group_wait() 等待group里的所有任务执行完毕后,继续执行(阻塞当前线程)
dispatch_group_notify() 将任务添加到队列中,当某个分组的所有任务执行完之后,此任务才会执行
dispatch_barrier_async() 将任务添加到队列中,此任务执行的时候,其他任务停止执行
dispatch_once() 任务添加到队列中,但任务在程序运行过程中,只执行一次
dispatch_sync() 将任务添加到队列中,block不执行完,下面代码不会执行
dispatch_async_f() 将任务添加到队列中,任务是函数非block
一些使用案例:
1、延迟执行:
代码语言:javascript复制dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
dispatch_after(time , dispatch_get_main_queue(), ^{
NSLog(@"延迟5s执行");
});
NSLog(@"是否阻塞主线程"); // 不会
2、只执行一次
代码语言:javascript复制static dispatch_once_t oncetoken;
dispatch_once(&oncetoken, ^{
NSLog(@"只执行一次");
});
可以用来实现单例,很多面试会考,最好能手写出来:
代码语言:javascript复制 (Class *)shareInstance {
static Class *instance = nil;
static dispatch_once_t oncetoken;
dispatch_once(&oncetoken, ^{
instance = [[Class alloc] init];
});
return instance;
}
3、在子线程中,返回主线程
代码语言:javascript复制dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程");
});
一、获取并发队列:
方法1:直接使用默认提供的`全局并发队列`
代码语言:javascript复制dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // (扩展参数:暂时用不上)
// DISPATCH_QUEUE_PRIORITY_HIGH 2 高
// DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认
// DISPATCH_QUEUE_PRIORITY_LOW (-2) 低
// DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台
方法2:自己创建
代码语言:javascript复制dispatch_queue_t queue = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_CONCURRENT);
二、获取串行队列:
方法1:直接使用主线程
代码语言:javascript复制dispatch_queue_t mainQueue = dispatch_get_main_queue();
方法2:自己创建
代码语言:javascript复制dispatch_queue_t queue1 = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_SERIAL);
下面来测试混合搭配使用:
1、异步-全局并发:
代码语言:javascript复制dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 不会
// 执行结果:
// 是否阻塞主线程
// 执行1:<NSThread: 0x600000a69280>{number = 5, name = (null)}
// 执行3:<NSThread: 0x600000a54e80>{number = 6, name = (null)}
// 执行2:<NSThread: 0x600000a58780>{number = 7, name = (null)}
// 完成3:<NSThread: 0x600000a54e80>{number = 6, name = (null)}
// 完成1:<NSThread: 0x600000a69280>{number = 5, name = (null)}
// 完成2:<NSThread: 0x600000a58780>{number = 7, name = (null)}
结论1 异步-全局并发:开启多个线程,并发执行,不阻塞
2、异步-并发
代码语言:javascript复制dispatch_queue_t queue = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 不会
// 执行结果:
// 执行1:<NSThread: 0x600002116400>{number = 5, name = (null)}
// 执行2:<NSThread: 0x60000211df80>{number = 6, name = (null)}
// 执行3:<NSThread: 0x600002116440>{number = 7, name = (null)}
// 是否阻塞主线程
// 完成3:<NSThread: 0x600002116440>{number = 7, name = (null)}
// 完成1:<NSThread: 0x600002116400>{number = 5, name = (null)}
// 完成2:<NSThread: 0x60000211df80>{number = 6, name = (null)}
结论2 异步-并发:开启多个线程,并发执行,不阻塞
3、异步-主串行
代码语言:javascript复制dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 不会
// 执行结果:
// 是否阻塞主线程
// 执行1:<NSThread: 0x60000088cdc0>{number = 1, name = main}
// 完成1:<NSThread: 0x60000088cdc0>{number = 1, name = main}
// 执行2:<NSThread: 0x60000088cdc0>{number = 1, name = main}
// 完成2:<NSThread: 0x60000088cdc0>{number = 1, name = main}
// 执行3:<NSThread: 0x60000088cdc0>{number = 1, name = main}
// 完成3:<NSThread: 0x60000088cdc0>{number = 1, name = main}
结论3 异步-主串行:主线程中,顺序执行,不阻塞
4、异步-串行
代码语言:javascript复制dispatch_queue_t queue = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 不会
// 执行结果:
// 是否阻塞主线程
// 执行1:<NSThread: 0x600003e45200>{number = 3, name = (null)}
// 完成1:<NSThread: 0x600003e45200>{number = 3, name = (null)}
// 执行2:<NSThread: 0x600003e45200>{number = 3, name = (null)}
// 完成2:<NSThread: 0x600003e45200>{number = 3, name = (null)}
// 执行3:<NSThread: 0x600003e45200>{number = 3, name = (null)}
// 完成3:<NSThread: 0x600003e45200>{number = 3, name = (null)}
结论4 异步-串行:开启一个线程,顺序执行,不阻塞
5、同步-全局并发
代码语言:javascript复制// (如果用async加入,不会跟sync的在一个线程里)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 会
// 执行结果:
// 执行1:<NSThread: 0x600000675880>{number = 1, name = main}
// 完成1:<NSThread: 0x600000675880>{number = 1, name = main}
// 执行2:<NSThread: 0x600000675880>{number = 1, name = main}
// 完成2:<NSThread: 0x600000675880>{number = 1, name = main}
// 执行3:<NSThread: 0x600000675880>{number = 1, name = main}
// 完成3:<NSThread: 0x600000675880>{number = 1, name = main}
// 是否阻塞主线程
结论5 同步-全局并发:在主线程中,顺序执行,阻塞
6、同步-并发
代码语言:javascript复制dispatch_queue_t queue = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 会 (因为:没有开启新线程)
// 执行结果:
// 执行1:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 完成1:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 执行2:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 完成2:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 执行3:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 完成3:<NSThread: 0x600000ad9580>{number = 1, name = main}
// 是否阻塞主线程
结论6 同步-并发:在主线程中,顺序执行,阻塞
7、同步-主串行
代码语言:javascript复制// 例:之前在百度面试遇到的题
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{ // 串行 同步队列
NSLog(@"2");
});
NSLog(@"3");
// 输出:1
// 3加入队列 2加入队列;FIFO:3等待2执行 而2在3的后面
// 所以造成死锁(crash: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
结论7 同步-主串行:死锁,阻塞
8、同步-串行
代码语言:javascript复制dispatch_queue_t queue = dispatch_queue_create("moxiaoyan", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"执行1:%@", [NSThread currentThread]);
sleep(2);
NSLog(@"完成1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行2:%@", [NSThread currentThread]);
sleep(3);
NSLog(@"完成2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"执行3:%@", [NSThread currentThread]);
sleep(1);
NSLog(@"完成3:%@", [NSThread currentThread]);
});
NSLog(@"是否阻塞主线程"); // 会
// 执行结果:
// 执行1:<NSThread: 0x600003371880>{number = 1, name = main}
// 完成1:<NSThread: 0x600003371880>{number = 1, name = main}
// 执行2:<NSThread: 0x600003371880>{number = 1, name = main}
// 完成2:<NSThread: 0x600003371880>{number = 1, name = main}
// 执行3:<NSThread: 0x600003371880>{number = 1, name = main}
// 完成3:<NSThread: 0x600003371880>{number = 1, name = main}
// 是否阻塞主线程
结论8 同步-串行:主线程中,顺序执行,阻塞
总结: 同步:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程; 异步:异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)
异步 async | 同步 sync | |
---|---|---|
全局并行 global_queue | 开启多个线程,并发执行,不阻塞 | 主线程中,顺序执行,阻塞 |
自创并行 CONCURRENT | 开启多个线程,并发执行,不阻塞 | 主线程中,顺序执行,阻塞 |
主串行 main_queue | 主线程中,顺序执行,不阻塞 | 死锁 !!!,阻塞 |
自创串行 SERIAL | 开启一个线程,顺序执行,不阻塞 | 主线程中,顺序执行,阻塞 |
同步 和 异步 添加只影响是不是阻塞当前线程,和任务的串行或并行执行没有关系
Demo github 地址