多线程编程(四)GCD

前文中,我们介绍了多线程的基本概念和多线程编程实现的两种方式,本文,我们介绍一下最后一种多线程编程工具,也是最重要的一种:GCD。


1. GCD简介

1.1 什么是GCD

GCD全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” ,纯C语言,提供了非常多强大的函数。GCD为我们编写代码提供了非常绝佳的体验。它是苹果公司为在多核心设备上实现多线程充分利用多核优势提供的解决方案,在很多技术中,如 RunLoop GCD都扮演了重要的角色。GCD全称Grand Central Dispatch,直译为调度中心,完全基于C语言编写。使用GCD,不需要管理线程,线程管理完全托管给GCD。把线程想象成的火车,我们只需要提交我们的运送任务,GCD会帮我们在合适的火车上运行任务。多线程编程变得如此简单,但对于初学者来讲,GCD学习会有一定难度。因为是纯C编写。所以没有对象的概念,学习GCD你可以忘掉封装继承多态等等概念。

1.2 GCD的主要优势

GCD提供很多超越传统多线程编程的优势:

  • 易用: GCD比之thread跟简单易用,程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文。
  • 效率: GCD是苹果公司为多核的并行运算提出的解决方案,GCD会自动利用更多的CPU内核(比如双核、四核)。GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
  • 性能: GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。

1.3 使用GCD需要注意的几点

  • GCD存在于libdispatch.dylib这个库中,这个调度库包含了GCD的所有的东西,但任何IOS程序,默认就加载了这个库,在程序运行的过程中会动态的加载这个库,不需要我们手动导入。

  • GCD是纯C语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法。
  • GCD中的函数大多数都以dispatch开头。

1.4 GCD中有2个核心概念

  • 队列:用来存放任务
  • 任务:执行什么操作

1.5 GCD的使用的2个步骤

(1)定制队列

(2)提交任务,确定想做的事情

注意:将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出

2. 创建队列

GCD中的一个重要组成部分就是队列,我们把各种任务提交给队列,队列根据它本身的类型以及当前系统的状态,添加到不同的线程中执行任务。线程的创建和管理都有GCD 本身完成,不需要我们参与。系统提供了很多定义好的队列:只管理主线程的main_queue,全局并行的 globle_queue。同时,我们也可以自定义自己的队列queue。

GCD的队列可以分为2大类型:
1. 并发队列(Concurrent Dispatch Queue)
   可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
2. 串行队列(Serial Dispatch Queue)
   让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)所有线程串行,或者只有一个线程,任务依次执行。

queue的类型为 dispatch_queue_t。

创建函数是:dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

第一个参数代表queue的名字,注意是char型指针,并非NSString;

第二个参数表示queue的类型,

  • DISPATCH_QUEUE_SERIAL表示串行 queue。
  • DISPATCH_QUEUE_CONCURRENT表 示并行queue。

2.1 串行队列

GCD中获得串行有两种途径

  • 使用主队列(跟主线程相关联的队列)

    主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。提交给main queue的任务可能不会立马被执行,而是在主线程的Run Loop检测到有dispatch 提交过来的任务时才会执行。

    使用dispatch_get_main_queue()函数获得主队列

    注意:如果把任务放到主队列中进行处理,那么不论处理函数是异步还是同步,都不会开辟新的线程。

    示例:dispatch_queue_t queue = dispatch_get_main_queue();

  • 使用dispatch_queue_create函数创建串行队列

    说明:dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 队列名称, 队列属性,一般用NULL即可,也可以设置参数DISPATCH_QUEUE_SERIAL

    示例:dispatch_queue_t queue = dispatch_queue_create(“队列”, NULL); // 创建

    注意:非ARC需要释放手动创建的队列,dispatch_release(queue);

2.2 并发队列

GCD中同样提供了两种途径获得串行

  • 使用全局队列

    GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建。也就是说我们可以直接提交给这个queue,任务会在非主线程的其他线程执行。

    使用dispatch_get_global_queue函数获得全局的并发队列

    说明:dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);

    示例:dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列。

    说明:全局并发队列的优先级

    define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高

    define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)

    define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低

    define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

    第二个参数参数是留给以后用的,暂时用不上,传个0。

获得全局并发Dispatch Queue (concurrent dispatch queue)

1. 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等。

2. 系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue

第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可

3. 虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue。因为这些queue对应用是全局的,retain和release调用会被忽略。你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue就行了。

  • 使用dispatch_queue_create函数创建并行队列

    和获取串行队列一样,我们同样可以使用dispatch_queue_create函数创建并行队列,区别在于填入的第二个单数和获取串行队列的有所不同。

    说明:dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 队列名称, 队列属性设置参数为DISPATCH_QUEUE_CONCURRENT

    示例:dispatch_queue_t queue = dispatch_queue_create(“队列”, DISPATCH_QUEUE_CONCURRENT);

    注意:非ARC需要释放手动创建的队列,dispatch_release(queue);

2.3 queue的内存管理

ARC环境下,不需要手动编写代码。 非ARC环境下需要使用 dispatch_retain()和 dispatch_release() 管理queue的引用计数。原则如同NSObject对象,计数为0时销毁queue。

代码实例:

dispatch_queue_t q_1 =  dispatch_queue_create("task3.queue.1",DISPATCH_QUEUE_SERIAL);
                  dispatch_release(q_1);

3. 提交任务

GCD中有2个用来执行任务的函数,两个函数的作用就是把右边的参数(任务)提交给左边的参数(队列)进行执行。

1. 用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
2. 用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  参数说明:queue,任务执行的队列
                    block,需要执行任务

同步和异步的区别:

  • 同步:在当前线程中执行,会等待任务完成,卡死当前线程
  • 异步:在另一条线程中执行,不需要等待任务完成,不会对当前线程产生影响

3.1 同步提交

同步提交函数为dispatch_sync(),两个参数:

  • 第一个参数表示提交到的queue
  • 第二个参数表示任务详情

    这里用block的方式描述一个任务,原因很简单,Block也是纯C实现的,而平时使用的Invocation或者target+selector方式都是面向对象的。

    注意,同步提交任务后,先执行完block,然后dispatch_sync()返回,这时候如果同步提交的任务过长会导致主线程的卡死。

3.2 异步提交

异步提交函数为dispatch_async(),两个参数:

  • 第一个参数表示提交到的queue
  • 第二个参数表示任务详情

    异步提交任务后,dispatch_async()函数直接返回,无需等待block执行结束。不会导致主线程的卡死。

3.3 同时提交多次任务

同时提交多个任务给queue的函数很简单:

 dispatch_apply(size_titerations,dispatch_queue_tqueue,void(^block)(size_t));
三个参数分别是任务数,目标queue,任务描述

这里注意描述任务的block会重复多次调用,每次会给我们一个参数,表示任务次序。多个任务的执行顺序取决于添加队列queue的形式,如果目标queue是串行的,那么任务会依次执行,如果queue是并行的,那么任务会并发的执行,打印的顺序就会被打乱。

3.4 死锁

同步提交在某种情况下会造成死锁,即卡死。 示例1:

dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_sync(mainQ, ^{
    NSLog(@"----");
});
NSLog(@"OK");

示例2:

dispatch_queue_t q_1 =  dispatch_queue_create("task3.queue.1",DISPATCH_QUEUE_SERIAL);
dispatch_async(q_1, ^{

    NSLog(@"current is in q_1");
// q_1 blcok q_1 q_1 dispatch_sync
block block dispatch_sync
dispatch_sync(q_1, ^{
NSLog(@"this is sync ");

});
   NSLog(@"this is sync ????");
});

综合上面两个示例代码,结论显而易见:在一个串行队列执行的代码中,如果向此队列同步提交一个任务,会造成死锁。为了避免出现死锁的情况,要求我们避免在串行队列中同步提交任务给本身的队列。

3.5 各种队列的执行效果

3.6 补充说明

学习GCD时,有4个术语比较容易混淆:同步、异步、并发、串行。

同步和异步决定了要不要开启新的线程

  • 同步:在当前线程中执行任务,不具备开启新线程的能力
  • 异步:在新的线程中执行任务,具备开启新线程的能力

并发和串行决定了任务的执行方式

  • 并发:多个任务并发(同时)执行
  • 串行:一个任务执行完毕后,再执行下一个任务

3.7 代码示例

(1)用异步函数往并发队列中添加任务

//1.获得全局的并发队列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     //2.添加任务到队列中,就可以执行任务
     //异步函数:具备开启新线程的能力
     dispatch_async(queue, ^{
         NSLog(@"下载图片1----%@",[NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });
     //打印主线程
    NSLog(@"主线程----%@",[NSThread mainThread]);

总结:同时开启三个子线程

(2)用异步函数往串行队列中添加任务

//打印主线程
     NSLog(@"主线程----%@",[NSThread mainThread]);

     //创建串行队列
     dispatch_queue_t  queue= dispatch_queue_create("wendingding", NULL);
     //第一个参数为串行队列的名称,是c语言的字符串
     //第二个参数为队列的属性,一般来说串行队列不需要赋值任何属性,所以通常传空值(NULL)

    //2.添加任务到队列中执行
    dispatch_async(queue, ^{
         NSLog(@"下载图片1----%@",[NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });

总结:会开启线程,但是只开启一个线程

(3)用同步函数往并发队列中添加任务

//打印主线程
    NSLog(@"主线程----%@",[NSThread mainThread]);

     //创建串行队列
     dispatch_queue_t  queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

     //2.添加任务到队列中执行
     dispatch_sync(queue, ^{
         NSLog(@"下载图片1----%@",[NSThread currentThread]);
     });
     dispatch_sync(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });
     dispatch_sync(queue, ^{
         NSLog(@"下载图片3----%@",[NSThread currentThread]);
     });
 }

总结:不会开启新的线程,并发队列失去了并发的功能

(4)用同步函数往串行队列中添加任务

NSLog(@"用同步函数往串行队列中添加任务");
     //打印主线程
     NSLog(@"主线程----%@",[NSThread mainThread]);

     //创建串行队列
     dispatch_queue_t  queue= dispatch_queue_create("wendingding", NULL);
     //2.添加任务到队列中执行
     dispatch_sync(queue, ^{
         NSLog(@"下载图片1----%@",[NSThread currentThread]);
     });
     dispatch_sync(queue, ^{
         NSLog(@"下载图片2----%@",[NSThread currentThread]);
     });
     dispatch_sync(queue, ^{
         NSLog(@"下载图片3----%@",[NSThread currentThread]);
     });
 }

总结:不会开启新的线程

(5)补充

补充:队列名称的作用:

将来调试的时候,可以看得出任务是在哪个队列中执行的。


4. queue的暂停和继续

我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行尚未block对象,使用 dispatch_resume函数继续dispatch queue。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。特别强调,需要我们保证挂起队列和重启队列的函数成对调用。

在非ARC中使用时需要注意:调用dispatch_suspend会增加queue的引用计数,调用 dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。

 // 挂起与重启任务
          dispatch_suspend(globe_queue);
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               dispatch_resume(globe_queue);
          });

5. Dispatch Group

   什么是dispatch group,就像我们在NSOperation中添加依赖一样,如果我们一个任务需要等待其他一些任务完成才能执行时,我们使用dispatch group是最轻松的解决方式。

使用队列组可以让图片1和图片2的下载任务同时进行,且当两个下载任务都完成的时候回到主线程进行显示。

5.1 设置任务执行顺序

   GCD设置任务执行顺序是通过Dispatch Group实现的。我们以吃火锅为例:
  • 首先创建group 与任务队列

    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t globleQ = dispatch_get_global_queue(0, 0);

  • 提交任务,并且把任务添加到group中

    dispatch_group_async(group, globleQ, ^{});

    注意:提交任务到group是没有同步提交的,只有异步提交

  • 提交最终任务到group,同样为异步提交,

    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{});

5.2 时间延迟

  • dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    使当前线程堵塞,一直等待group所有任务结束:

  • dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (300 *NSEC_PER_SEC));
  • dispatch_group_wait(group, time); NSLog(@”“);

    也可以自定义超时时间,只等待一定的时间之后group内的任务没有执行的直接停止


6. GCD的常用方法

6.1 延迟执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

//延迟执行的方法

});

说明:在5秒钟之后,执行block中的代码段。

参数说明:

6.2 一次性执行(常用与单例设置)

使用dispatch_once一次性代码

使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

// 只执行1次的代码(这里面默认是线程安全的)

});

整个程序运行过程中,只会执行一次。

7. 小结

说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。

同步函数

(1)并发队列:不会开线程

(2)串行队列:不会开线程

异步函数

(1)并发队列:能开启N条线程

(2)串行队列:开启1条线程

补充: 在非ARC工程中,凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。

GCD的数据类型在ARC的环境下不需要再做release。

CF(core Foundation)的数据类型在ARC环境下还是需要做release。

异步函数具备开线程的能力,但不一定会开线程

时间: 2024-11-06 11:18:19

多线程编程(四)GCD的相关文章

多线程编程4 - GCD

一.简介 在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决方案.GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器.GCD是Grand Central Dispatch的简称,它是基于C语言的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue).GCD会负责创建线程和调度你的任务,系统直接提供线程管理 二.调度队列(dispath qu

iOS开发——多线程编程(GCD)

Grand Central Dispatch简介 Grand Central Dispatch 简称 GCD 是苹果公司开发的技术,是对于多核编程的较新解决方案.它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统. GCD 提供了一种很简单的操作方式来实现并行处理.你可以把你要的并发执行的代码放在一个block钟,然后把这个block加入到一个queue当中. 在GCD中为我们需要执行的block提供了3种队列: Main:这个队列顺序执行我们的block,并保证这些block都在主线

iOS多线程编程(四)------ GCD(Grand Central Dispatch)

一.简介 是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法,用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch_queue).GCD会负责创建线程和调度你的任务,系统会直接提供线程管理. 二.任务和队列 GCD中有两个核心概念 (1)任务:执行什么操作 (2)队列:用来存放任务 GCD的使用就

线程同步-iOS多线程编程指南(四)-08-多线程

首页 编程指南 Grand Central Dispatch 基本概念 多核心的性能 Dispatch Sources 完结 外传:dispatch_once(上) Block非官方编程指南 基础 内存管理 揭开神秘面纱(上) 揭开神秘面纱(下) iOS多线程编程指南 关于多线程编程 线程管理 Run Loop 线程同步 附录 Core Animation编程指南 Core Animation简介 基本概念 渲染架构 几何变换 查看目录 中文手册/API ASIHTTPRequest Openg

【iOS沉思录】NSThread、GCD、NSOperation多线程编程总结

OC中的多线程 OC中多线程根据封装程度可以分为三个层次:NSThread.GCD和NSOperation,另外由于OC兼容C语言,因此仍然可以使用C语言的POSIX接口来实现多线程,只需引入相应的头文件:#include <pthread.h>. NSThread NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期.线程同步和线程加锁等,开销较大: NSThread的基本使用比较简单,可以动态创建初始化NSThread对象,对其进行设置然后启动:也可以通过NST

多线程编程 - GCD

多线程编程 - GCD 什么是GCD? GCD本身是苹果公司为多核的并行运算提出的解决方案.GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器.GCD是Grand Central Dispatch的简称,它是基于C语言的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue).GCD会负责创建线程和调度你的任务,系统直接提供线程管理 GCD的优势 GCD是苹果公司为多核的并行运算提出的解决方案GC

Android中多线程编程(四)AsyncTask类的详细解释(附源码)

Android中多线程编程中AsyncTask类的详细解释 1.Android单线程模型 2.耗时操作放在非主线程中执行 Android主线程和子线程之间的通信封装类:AsyncTask类 1.子线程中更新UI 2.封装.简化异步操作. 3.AsyncTask机制:底层是通过线程池来工作的,当一个线程没有执行完毕,后边的线程是无法执行的.必须等前边的线程执行完毕后,后边的线程才能执行. AsyncTask类使用注意事项: 1.在UI线程中创建AsyncTask的实例 2.必须在UI线程中调用As

apple平台下的objc的GCD,多线程编程就是优雅自然。

在apple的操作系统平台里,GCD使得多线程编程是那么的优雅自然.在传统的多线程编程中,首先要写线程处理循环:之后还有事件队列,消息队列:还要在线程循环中分离事件解释消息,分派处理:还要考虑线程间是否要同步:还要写许多有着可能费解的函数名的回调处理程序,注册回调程序,而且代码分散即使同一文件也不容易看出与哪些线程对应或者彼此间的是否有次序或并发的关系,不利于调试:另外还要考虑是否需要使用线程池,线程线使用何种模式等.在apple平台的objc中,只需要如下: A* a; dispatch_as

【转】iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

转自容芳志的博客 简介 iOS有三种多线程编程的技术,分别是: (一)NSThread (二)Cocoa NSOperation (三)GCD(全称:Grand Central Dispatch) 这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的. 三种方式的优缺点介绍: 1)NSThread: 优点:NSThread 比其他两个轻量级 缺点:需要自己管理线程的生命周期,线程同步.线程同步对数据的加锁会有一定的系统开销 NSThread实现的技术