iOS_多线程一:GCD+混搭测试

2022-07-20 14:08:41 浏览数 (1)

一、基础概念

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 地址

0 人点赞