多线程开发2 -GCD

来源:互联网 发布:网络虚拟结婚 编辑:程序博客网 时间:2024/06/06 20:17

GCD

1.简介

全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”,基于C语言开发的多线程并发的面向过程技术;GCD是苹果为多核的并行运算提出的解决方案,使它对于多核运算更加有效。可以自动管理线程的生命周期;抽象层次更高,我们只需告诉它做什么,不需要编写多余的线程管理代码;

2.核心概念

用法: 将任务添加到队列,并且指定执行任务的函数 即可;

  1. 任务:执行什么操作 (block封装的内容);
  2. 队列:用来存放任务 (先进先出顺序);
  3. 执行函数: 同步执行dispatch_sync ; 异步执行dispatch_async ;

线程池概念:
实现原理: 线程池在开启线程之后永不销毁,当需要让子线程执行新的方法(使用performSelector让指定的方法在指定的子线程上运行),此线程执行任务完毕后,会放到线程池存放很短时间,如果有新任务,系统会优先调用池中的线程执行,如果没有,很快自动销毁;

3. 队列

队列类型:

  • 串行队列:只有一个线程,一个任务执行完毕,再执行下一个
  • 并发队列:可以有多个线程,以 先进先出 的方式,并发 调度队列中的任务 去执行
  • GCD中还有一个特殊队列就是主队列,用来执行主线程上的操作任务
3.1 串行队列

通常只用串行异步组合,用于有序执行耗时的操作;

//创建队列dispatch_queue_t queue = dispatch_queue_create("name",DISPATCH_QUEUE_SERIAL); // 宏定义 为NULL;所以直接可以用NULL做参数;for (int i=0; i<10;i++) {    dispatch_async(queue,^{    xxxxxxxxxx    }); //异步,会开启一个线程,有序执行任务   //dispatch_sync(queue,^{    xxxxxxxxxx    });//同步,不会开启新线程,在主线程上有序执行;}

注意:无论队列中所指定的执行任务的函数是同步还是异步,都必须等待前一个任务执行完毕,才可以调度后面的任务

3.2 并行队列

创建使用DISPATCH_QUEUE_CONCURRENT参数.实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue(0,0)方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建;)

注意:

如果当前调度的任务是 同步 执行的,不开线程顺序执行;所以不常使用
如果当前调度的任务是 异步 执行的,开多条线程,取决于底层线程池.乱序执行

全局队列与普通并行的简单区别:

  1. 全局唯一,没有名字.
  2. 全局无论 MRC & ARC 都不需要考虑内存释放,并行在MRC下需要手动dispatch_release(q)
  3. dispatch_barrier阻塞 必须使用自定义的并发队列;另外,开发第3方框架时,建议使用并发队列
  4. 全局队列的参数:第一个long identifier:表示服务质量:枚举(iOS8之后)
    • 用户交互(希望最快完成-不能用太耗时的操作)
    • 用户期望(希望快,也不能太耗时)
    • 默认(用来底层重置队列使用的,不是给程序员用的)
    • 实用工具(专门用来处理耗时操作!)
      第二个参数是为未来使用保留的,始终传入0
  5. 所以iOS7和iOS8适配只能使用 0,0参数dispatch_get_global_queue(0, 0);
3.3 主队列

以 先进先出 的方式, 只有在主线程空闲时 才会调度队列中的任务,只在主线程执行;
用dispatch_queue_t queue = dispatch_get_main_queue();获取
对主队列而言,不管是同步还是异步,都不会开线程

注意:
如果主线程当前有任务正在执行,那么无论主队列中当前被添加了什么任务,都不会被调度.只有当主线程空闲了,主队列才会调度任务到主线程上执行 .(所以如果调用同步主队列,会造成死锁;只有在子线程中才能这样调用:)

  //子线程中dispatch_async(dispatch_get_global_queue(0, 0), ^{    // 1. 获取主队列    dispatch_queue_t q = dispatch_get_main_queue();    // 2. 将任务添加到主队列, 并且指定同步执行    // 死锁    for (int i = 0; i < 10; i++) {        dispatch_sync(q, ^{            NSLog(@"%@ %d", [NSThread currentThread], i);        });    }});

队列总结:

  1. 开不开线程,由执行任务的函数决定;只有异步才开新线程;
  2. 而在异步执行任务,开几条线程由队列决定:串行队列, 只会开一条线程;并发队列, 可以开多条线程,具体开几条由线程池决定.

4.解决三大问题方式

4.1 解决耗时操作的线程阻塞.

耗时操作异步执行,回主线程中更新UI

dispatch_async(dispatch_get_global_queue(0, 0), ^{    NSLog(@"hello %@", [NSThread currentThread]);    dispatch_sync(dispatch_get_main_queue(), ^{  //回主线程更新UI控件        imageView.image = image;       });});

4.2 解决线程依赖-线程同步

1.直接按序

dispatch_async(dispatch_get_global_queue(0, 0), ^{//并行异步(开新线程) -无序执行    dispatch_sync(dispatch_get_global_queue(0, 0), ^{//并行同步                                     NSLog(@"输入密码 %@",[NSThread currentThread]);    });    dispatch_sync(dispatch_get_global_queue(0, 0), ^{        NSLog(@"扣费 %@",[NSThread currentThread]);    });    dispatch_async(dispatch_get_global_queue(0, 0), ^{//并行异步            NSLog(@"下载应用 %@",[NSThread currentThread]);        });});

2.使用Barrier 注意:阻塞主要作用不是线程依赖,而是在多个异步操作之后,统一对非线程安全对象进行更新且不能用全局队列

//创建并发队列.dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);for (int i = 0 ; i<10; i++) {    dispatch_async(queue,^{        for (int i = 0 ; i<10; i++) {        dispatch_barrier_async(queue,^{           NSLog(@"保存图片");         });      }     NSLog(@"下载图片");    }) }//所有图片下载完毕,再执行保存图片;

3.调度组.可以做到阻塞效果,不过调度组主要是用于在多个异步操作之后;可以转到别的线程(主线程)提示用户

//1.获取队列dispatch_queue_t queue = dispatch_get_global_queue(0,0);//2.创建调度组dispatch_group_t group = dispatch_group_create();//3.添加任务 到 队列dispatch_group_async(group,queue,^{    NSLog(@"歌曲1");});dispatch_group_async(group,queue,^{    NSLog(@"歌曲2");});dispatch_group_async(group,queue,^{    NSLog(@"歌曲3");});//4所有任务都异步执行完成后,获得通知;dispatch_group_notify(group,queue,^{   //最后执行. queue可以换成main_queue,更新UI,通知用户等});

4.3 解决资源抢夺–(线程是不安全的)

//同NSThread 加互斥锁@synchronized (self)  {    name=[_imageNames lastObject];    [_imageNames removeObject:name];}

5. CGD的其他应用

5.1 延迟执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2*NSEC_PER_SEC)),dispatch_get_main_queue(),^{   ---------   });//参数1 dispatch_time () 延迟时间//参数2 任务添加到哪个队列//参数3 要执行的block任务

5.2 一次执行(程序中只执行一次)

原理:在当前线程上执行,判断静态全局变量值, 默认0,如果执行完成后,设为1;once内部会对这个值进行判断.

static dispatch_once_t onceToken;dispatch_once(&onceToken,^{    NSLog(@"只执行一次");});

一般用来定义单例;

+(instancetype)sharexxxxOnce {    static id instance = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken,^{        if (instance == nil) {            instance = [self alloc] init];        }    });    return instance;}

注意:如果要严谨的话,还要重新allocWithzone方法和copyWithzone方法;

+ (instancetype)sharedNetworkToolsOnce {    return [[self alloc] init];}- (id)copyWithZone:(nullable NSZone *)zone{    return self;}+ (instancetype)allocWithZone:(struct _NSZone *)zone{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        instance = [super allocWithZone:zone];    });    return instance;}

5.3调度组原理

//1.获取队列dispatch_queue_t queue = dispatch_get_global_queue(0,0);//2.创建调度组dispatch_group_t group = dispatch_group_create();//ARC中不用写//dispatch_retain(group); //3 进入调度组,执行此函数后,再添加的异步 执行的block都会被group监听dispatch_group_enter(group);//4 添加任务dispatch_async(queue, ^{NSLog(@"!!--!!");dispatch_group_leave(group);//ARC中不用写dispatch_release(group);});dispatch_group_enter(group);dispatch_async(queue, ^{NSLog(@"!!------!!");dispatch_group_leave(group);});//5  获得调度组的通知dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"okkkkkkk   %@",[NSThread currentThread]);});//6 等待调度组 监听的队列中的所有任务全部执行完毕,才会执行后续代码,会阻塞线程(很少使用)
0 0
原创粉丝点击