博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS GCD入门和GCD对CPU多核的使用
阅读量:6994 次
发布时间:2019-06-27

本文共 11506 字,大约阅读时间需要 38 分钟。

前言

明天要给师弟开分享会,分享GCD。 好方,只理解一些皮毛拿什么去装。准备的时候顺便把过程记录下来。

目录

  • 概念
  • 简单了解用法
  • 开发中常用的做法
  • GCD其他的一些API
  • GCD会遇到的问题

和GCD有关的基本概念

术语 含义
进程 打开一个App就是开启一个进程。
线程 独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。在iOS系统中,一个进程包含一个主线程,它的主要任务是处理UI。其他线程称为子线程。
同步 A执行完再执行B。
异步 A和B可以同时执行。
任务 可以理解为某一堆要执行的代码。分为同步执行任务和异步执行任务。用block定义。
同步执行任务 按进入顺序执行的任务
异步执行任务 不管进入顺序,可以一起执行
队列 存放任务的结构。分为串行队列和并行队列。遵循先进先出。
队列组 将多线程进行分组,最大的好处是可获知所有线程的完成情况。
串行队列 线程执行只能依次逐一先后有序的执行。
并行队列 指两个或多个事件在同一时刻发生。多核CUP同时开启多条线程供多个任务同时执行,互不干扰。
并发 指两个或多个事件在同一时间间隔内发生。可以在某条线程和其他线程之间反复多次进行上下文切换,看上去就好像一个CPU能够并且执行多个线程一样。其实是伪异步。
  • 一个有助于判断执行完成时间的理论。 开线程需要消耗内存,所以要消耗时间

回头看觉得有必要在这简单说明多线程 多核 并发并行的区别子线程和主线程的联系

最近玩了个游戏叫《Inside》,戴着头盔就能操纵机器人,感觉无论是玩法还是游戏剧情都超适合类比线程。 用这个举个例子。 假如你是国王,拿到了一张藏宝图,但这个宝藏要到每一个地点才能得知下一个地点的信息(电路中内存地址)。于是你就操纵机器人A去找,找到后带回来。机器人A的路线就是一条线程。 当机器人A还在路程上,你又得到一张藏宝图。你这时候派机器人B去找,找到带回来。这时候机器人B的路线就是另一条线程。 以上就是多线程。 这时候,只要你周期足够短,轮流戴头盔a和头盔b,,看上去就像你同时在操纵机器人A和机器人B。这就叫做并发!装出来的。 某一天,你的头快摇傻了。于是乎你长出了第二个头。(对应着双核CPU),这时候就是名副其实地同时操纵。这就叫并行,必须要多头怪才拥有这技能。 但如果又操纵第三个机器人,这时候只能再来回戴了,又要并发了。 A找到并回到了城堡把结果带回给你,你才发现你也是个机器人(主线程)。其他机器人带回宝藏后就可以拜拜了,但就算还有没有宝藏在路上,你都不能拜拜,必须保持呼吸(runloop)。 这就是子线程和主线程的联系。 子线程的任务全部完成后,最终会回到主线程。主线程中运行着runloop

简单了解用法

就是把任务加到队列中 队列可以自己新建。 系统也有 全局并发队列主队列

#pragma mark - 创建队列//  创建队列//  第一个参数 队列名称//  第二个参数的作用:串行(DISPATCH_QUEUE_SERIAL)、并行(DISPATCH_QUEUE_CONCURRENT)。  dispatch_queue_t queue = dispatch_queue_create("net.Hsusue.testQueue", DISPATCH_QUEUE_CONCURRENT);* 常用的系统并发队列——全局并发队列//程序默认的队列级别,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//HIGHdispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);//LOWdispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);//BACKGROUNDdispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);// 获取主队列有特别函数(是个串行队列)//  dispatch_queue_t queue = dispatch_get_main_queue();#pragma mark - 创建任务加到队列中//  同步执行任务创建方法  dispatch_sync(queue, ^{  // 这里放同步执行任务代码  });// 异步执行任务创建方法  dispatch_async(queue, ^{  // 这里放异步执行任务代码  });复制代码

个人认为易迷惑的点

  • 太多的组合方式 有两种任务执行方式,两种队列+特殊的主队列,就可以组成六种组合。 有两张图总结得特别好,记住这两张图,分析的时候用得到。 然后为了更好理解,自己也花了点时间弄了动图。

还是不能忘了《Inside》的例子。

  • 两种待办任务表(对应队列) 一种是多个机器人对多个宝藏,先入先出发。(对应并行队列) 另一种是一个机器人对有顺序找的多个宝藏。(对应串行队列) 特殊的 强行自己去做的任务表。 (对应主队列)

  • 你有两类事情(对应任务) 一类是吃喝拉撒,一有需要就自己马上去做,总不能懒到让机器人帮忙吧。(对应着同步执行任务) 另一类是寻宝,要机器人去做,出发前要点时间给机器人充电。(对应着异步执行任务)

代码中, 输出@"1"对应着吃喝拉撒

  1. 异步 + 并行队列 (多个机器人找多个宝藏)
- (void)viewDidLoad {    [super viewDidLoad];        [self asyncConcurrent];    NSLog(@"1");}//异步执行 + 并行队列- (void)asyncConcurrent{    //创建一个并行队列    dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_CONCURRENT);        NSLog(@"---start---");        //使用异步函数封装三个任务    dispatch_async(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });        NSLog(@"---end---");}复制代码

多个机器人找多个宝藏,完成程度和你的吃喝拉撒没必然先后顺序。

  1. 异步 + 串行队列 (一个机器人找有序宝藏)
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];    [self asyncSerial];    NSLog(@"1---%@", [NSThread currentThread]);}//异步 + 串行队列- (void)asyncSerial{    //创建一个串行队列    dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_SERIAL);        NSLog(@"---start---");    //使用异步函数封装三个任务    dispatch_async(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });    NSLog(@"---end---");}复制代码

一个机器人等有序宝藏图拼接好后,就出发了。和你吃喝拉撒没先后顺序。

  1. 同步 + 并行队列 (自己吃喝拉撒 放到 多个机器人对多个宝藏,准备好后机器人一起出发)
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];    [self syncConcurrent];    NSLog(@"1---%@", [NSThread currentThread]);}//同步 + 并行队列- (void)syncConcurrent{    //创建一个并行队列    dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_CONCURRENT);        NSLog(@"---start---");    //使用同步函数封装三个任务    dispatch_sync(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });    NSLog(@"---end---");}复制代码

一要吃喝拉撒就自己马上去做。所以不等@“end”输出就先做完了。最后再@“1”。有着必然先后顺序。

  1. 同步+ 串行队列 (自己吃喝拉撒 放到 一个机器人对有顺序找的多个宝藏)
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];//    [self syncConcurrent]    [self syncSerial];    NSLog(@"1---%@", [NSThread currentThread]);}//同步 + 串行队列- (void)syncSerial{    //创建一个串行队列    dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_SERIAL);        NSLog(@"---start---");    //使用异步函数封装三个任务    dispatch_sync(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });    NSLog(@"---end---");}复制代码

这次更过分了,试图让一个机器人帮自己拉三次尿。。。但机器人做不到。

一要吃喝拉撒就自己马上去做。所以不等@“end”输出就先做完了。最后再@“1”。有着必然先后顺序。

  1. 异步 + 主队列 (让机器人充电准备寻宝 放到 强行自身去做的任务表 )
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];//    [self syncConcurrent]//    [self syncSerial];    [self asyncMain];    NSLog(@"1---%@", [NSThread currentThread]);}//异步 + 主队列- (void)asyncMain{    //获取主队列    dispatch_queue_t queue = dispatch_get_main_queue();        NSLog(@"---start---");    //使用异步函数封装三个任务    dispatch_async(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });    NSLog(@"---end---");}复制代码

和异步 + 串行队列区别就是不开启新线程。

让机器人充电准备,所以自己先吃喝拉撒完。直到@"1"。 然后你发现这件事是在强制自己做的任务表上,于是就自己一件接一件做了。

  1. 同步+主队列(死锁)(吃喝拉撒 + 强行自身去做)
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];//    [self syncConcurrent]//    [self syncSerial];//    [self asyncMain];    [self syncMain];    NSLog(@"1---%@", [NSThread currentThread]);}//同步+主队列(死锁)- (void)syncMain{    //获取主队列    dispatch_queue_t queue = dispatch_get_main_queue();        NSLog(@"---start---");    //使用同步函数封装三个任务    dispatch_sync(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务B---%@", [NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"任务C---%@", [NSThread currentThread]);    });    NSLog(@"---end---");}复制代码

这里认真分析一下死锁的原因,不用那个例子了。 先说点计算机组成原理的知识,虽然我也学得很烂。
计算机指令包括操作码和地址码。
每个函数进入都会记住进入的地址码,return时就会回去。

上面主队列在主队列中加了任务。 实质在同一个同步串行队列中,再使用该串行队列同步的执行任务。

[self syncMain]这是主队列做(出)的事(同步且未做完)。根据先进先出,主队列头是syncMain。然后假设这里的内存地址是1。

dispatch_sync(queue, ^{        NSLog(@"任务A---%@", [NSThread currentThread]);    });// 假设运行时此处内存地址为1复制代码

添加了一个block到主队列尾部,要等主队列头synMain执行完才能执行。 本来应该执行追加任务B,但是电路上的地址并没有回来,因为dispatch_sync要执行完block才reutrn。 因为被代码被黑盒子包起来了,大胆猜测一下。 假设内存地址为2

// 调用时记住进入地址为1dispatch_sync {  // block执行完才return  // 运行时此处内存地址为2   if( block() ) { // block执行完     return;//返回到进入地址   }}复制代码

于是代码可以看成 卡在了该函数内部,内存地址为2处。 没有回到1处,自然就不会追加任务B。

开发中常用的做法

上面说了很多种方法,禁止死锁情况开发中是很容易记住的。 但其他组合,即使想的时候能想懂,但也还是很混乱。 根据我个人经验,日常开发中先从宏观上想是否需要耗时(耗时放到子线程),是否有序。 通常是需要和主线程同时执行(开新线程,即异步执行任务)才会用到GCD。 可能是开发经验不够。

  • 异步 + 并行或串行。 举个例子。

从子线程,异步返回主线程更新UI。 队列常用全局并行队列。 因为要下载图片耗时,而且具有网络不稳定性,所以放到子线程。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_async(globalQueue, ^{        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3948453733,2367168123&fm=27&gp=0.jpg"]];        UIImage *image = [UIImage imageWithData:imgData];        dispatch_queue_t mainQueue = dispatch_get_main_queue();        dispatch_async(mainQueue, ^{            UIImageView *imgView = [[UIImageView alloc]initWithImage:image];            [imgView setFrame:CGRectMake(0, 0, 200, 200)];            [imgView setCenter:self.view.center];            [self.view addSubview:imgView];        });    });复制代码
  • 队列组 队列组能获知队列完成程度。 同时下载多个图片,所有图片下载完成之后去更新UI。
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];//    [self syncConcurrent]//    [self syncSerial];//    [self asyncMain];//    [self syncMain];    [self groupTest];    NSLog(@"1---%@", [NSThread currentThread]);}- (void)groupTest {    dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_queue_t mainQueue = dispatch_get_main_queue();    dispatch_group_t groupQueue = dispatch_group_create();    NSLog(@"current task");    dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{        NSLog(@"并行任务1");    });    dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{        NSLog(@"并行任务2");    });    dispatch_group_notify(groupQueue, mainQueue, ^{        NSLog(@"groupQueue中的任务 都执行完成,回到主线程更新UI");    });    NSLog(@"next task");}复制代码

1.
dispatch_group_t groupQueue = dispatch_group_create();用于生成队列组 2.生成队列时加上前缀_guoup 3.
dispatch_group_notify这个函数用以处理其他队列完成的块。

GCD其他的API

  • dispatch_once:这个函数保证在应用程序执行中只执行一次指定处理的API。(见过用于音乐播放器单例)
static dispatch_once_  onceToken;dispatch_once( &onceToken,^{对象A =[ [对象A  alloc] init];});复制代码
  • dispatch_barrier_async:栅栏方法。用于在同一个队列中,阻断前后的任务。
- (void)viewDidLoad {    [super viewDidLoad];    //    [self asyncConcurrent];//    [self asyncSerial];//    [self syncConcurrent]//    [self syncSerial];//    [self asyncMain];//    [self syncMain];//    [self groupTest];    [self barrier];    NSLog(@"1---%@", [NSThread currentThread]);}// 栏栅函数- (void)barrier {    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);    dispatch_async(queue, ^{        NSLog(@"任务A---%@",[NSThread currentThread]);          });    dispatch_async(queue, ^{        NSLog(@"任务B---%@",[NSThread currentThread]);    });    dispatch_barrier_async(queue, ^{        NSLog(@"栏栅函数---%@",[NSThread currentThread]);    });//  换成同步执行也一样//  dispatch_barrier_sync(queue, ^{//        NSLog(@"栏栅函数---%@",[NSThread currentThread]);//    });    dispatch_async(queue, ^{        NSLog(@"任务C---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"任务D---%@",[NSThread currentThread]);    });}复制代码

可以这么理解

  • dispatch_after:延时执行方法,时间并不精准。我常用其他延时方法,不展开谈论这个。
  • dispatch_apply:快速迭代方法。 for必须按顺序同步遍历,dispatch_apply可以同时遍历多个数字。相当于开线程遍历。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);    dispatch_apply(10, queue, ^(size_t i) {        NSLog(@"%zd----%@", i, [NSThread currentThread];    }复制代码
  • 还有一些别的不常用就不展开了。

GCD会遇到的问题

  • 死锁 上面解释过了
  • 线程安全 场景:两条不同的线程之间同时对一个数据I/O。 比如商品数量 count = 10 , 单价price = 2 单件重量 = 0.1 A线程要取某个商品数量,算出商品总价,商品总重量。 B线程修改商品数量。 假如A先算出商品总价20,这时B突然修改了count = 11,那A算出的重量是1.1,而不是期望的10。 解决方法: 先简单理解线程和runloop。主线程必定会开一条runloop。但子线程默认是不开启的。开启了runloop就会执行某个机制,让线程在循环,不至于销毁。 所以我们可以在A访问到count时,对count加锁,别的线程只可以取值,不可以写入。这时别的线程如果访问不到,就会开启runloop,不定时访问,看看count解锁没有。 加锁方法 方法一 互斥锁(同步锁)
@synchronized(锁对象) {    // 需要锁定的代码}复制代码

判断的时候锁对象要存在,如果代码中只有一个地方需要加锁,大多都使用self作为锁对象,这样可以避免单独再创建一个锁对象。 方法二:自旋锁 用到属性修饰原子属性nonatomicatomic非原子属性

  • atomic:保证同一时间只有一个线程能够写入,读取随意
  • nonatomic:同一时间可以有很多线程读和写 atomic带有自旋锁,别的线程如果写入,就会开启runloop。 但是循环执行的线程,会消耗不少资源。所以一般开发中,除非确定不然不要用atomic。

参考

力荐第三篇,看了很多瞎说的,就这篇真实!!!

转载地址:http://twdvl.baihongyu.com/

你可能感兴趣的文章
将excel中的数据转为json格式
查看>>
字典操作
查看>>
[洛谷P2839][国家集训队]middle
查看>>
《求一个数组的连续的最大子数组之和》
查看>>
设置行间距,自适应文字大小
查看>>
资金流学习-广州发展
查看>>
python基础3(元祖、字典、深浅copy、集合、文件处理)
查看>>
正确编写Designated Initializer的几个原则
查看>>
iOS播放动态GIF图片
查看>>
获取版本号
查看>>
使用jdk自带的visualVM监控远程监控was
查看>>
集合视图UICollectionView 介绍及其示例程序
查看>>
JsLint 的安装和使用
查看>>
合并傻子//区间dp
查看>>
让IE和Chrome都以隐身模式启动
查看>>
MyPython-->进阶篇-->类
查看>>
unity remote 连接设置
查看>>
2018 NOIP备战计划
查看>>
教你如何迅速秒杀掉:99%的海量数据处理面试题
查看>>
zw版【转发·台湾nvp系列Delphi例程】HALCON InpaintingCt2
查看>>