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

GCD优势

GCD可用于多核的并行运算

GCD会自动利用更多的CPU内核(比如双核、四核)

GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

任务和队列

GCD中的两个核心概念

任务:执行什么操作,在GCD就是一个Block,任务执行的两种方式:同步执行和异步执行。主要区别在于是否具备开启新线程的能力

同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力

异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力

队列:存放任务,队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并行队列。

注意:

放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
 

并行队列(Concurrent Dispatch Queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)

并行功能只有在异步(dispatch_async)函数下才有效

串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

GCD的使用步骤

1、创建一个队列(串行队列或并行队列)

2、将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)1. 队列的创建方法

可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。

// 串行队列的创建方法

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

// 并行队列的创建方法

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

对于并行队列,还可以使用dispatch_get_global_queue来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

2. 任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
既然有两种队列,两种执行任务的方式,就有四种不同的组合方式
还有一种特殊的队列是主队列(串行队列),这样就有六种组合方式
  同步执行(sync) 异步执行(async)
并行队列 不会开启新的线程,在当前线程中任务一个接一个执行 启新的线程,所有线程并行执行任务
串行队列 不会开启新的线程,在当前线程中任务一个接一个执行 只开启一条新的线程,在新的线程中任务一个接一个执行
主队列 不会开启新的线程,在主线程中任务一个接一个执行 不会开启新的线程,在主线程中任务一个接一个执行

并行队列+同步执行

+(void)syncConcurrent

{

NSLog(@"syncConcurrent---begin");

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"1------%@",[NSThread currentThread]);

}

});

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"2------%@",[NSThread currentThread]);

}

});

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"3------%@",[NSThread currentThread]);

}

});

NSLog(@"syncConcurrent---end");

}

执行结果

2016-03-13 19:03:42.539 多线程_Demo[3812:271447] syncConcurrent---begin


2016-03-13 19:03:42.539 多线程_Demo[3812:271447] 1------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.539 多线程_Demo[3812:271447] 1------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.539 多线程_Demo[3812:271447] 2------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.540 多线程_Demo[3812:271447] 2------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.540 多线程_Demo[3812:271447] 3------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.540 多线程_Demo[3812:271447] 3------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:03:42.540 多线程_Demo[3812:271447] syncConcurrent---end

从执行结果可以看出,所有任务都是在主线程中执行,由于只有一个线程,所以所有的任务只能一个一个执行

并行队列+异步执行

+(void)asyncConcurrent{

NSLog(@"asyncConcurrent---begin");

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"1------%@",[NSThread currentThread]);

}

});

dispatch_async(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"2------%@",[NSThread currentThread]);

}

});

dispatch_async(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"3------%@",[NSThread currentThread]);

}

});

NSLog(@"asyncConcurrent---end");

}

执行结果

2016-03-13 19:06:22.064 多线程_Demo[3812:271447] asyncConcurrent---begin


2016-03-13 19:06:22.064 多线程_Demo[3812:271447] asyncConcurrent---end


2016-03-13 19:06:22.064 多线程_Demo[3812:271569] 1------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:06:22.064 多线程_Demo[3812:273918] 2------<NSThread: 0x60000026ae00>{number = 4, name = (null)}


2016-03-13 19:06:22.064 多线程_Demo[3812:273919] 3------<NSThread: 0x61800007cd80>{number = 5, name = (null)}


2016-03-13 19:06:22.064 多线程_Demo[3812:271569] 1------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:06:22.064 多线程_Demo[3812:273918] 2------<NSThread: 0x60000026ae00>{number = 4, name = (null)}


2016-03-13 19:06:22.064 多线程_Demo[3812:273919] 3------<NSThread: 0x61800007cd80>{number = 5, name = (null)}

从执行结果可以看出,除了主线程,有开启了3个新的线程,所有的线程并行执行任务

串行队列+同步执行

+ (void) syncSerial

{

NSLog(@"syncSerial---begin");

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"1------%@",[NSThread currentThread]);

}

});

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"2------%@",[NSThread currentThread]);

}

});

dispatch_sync(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"3------%@",[NSThread currentThread]);

}

});

NSLog(@"syncSerial---end");

}

执行结果

2016-03-13 19:07:11.269 多线程_Demo[3812:271447] syncSerial---begin


2016-03-13 19:07:11.269 多线程_Demo[3812:271447] 1------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.269 多线程_Demo[3812:271447] 1------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.270 多线程_Demo[3812:271447] 2------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.270 多线程_Demo[3812:271447] 2------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.270 多线程_Demo[3812:271447] 3------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.270 多线程_Demo[3812:271447] 3------<NSThread: 0x61000007f040>{number = 1, name = main}


2016-03-13 19:07:11.270 多线程_Demo[3812:271447] syncSerial---end

从执行结果可以看出,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行

串行队列+异步执行

+(void)asyncSerial{

NSLog(@"asyncSerial---begin");

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"1------%@",[NSThread currentThread]);

}

});

dispatch_async(queue, ^{

for (int i = 0; i < 2; ++i) {

NSLog(@"2------%@",[NSThread currentThread]);

}

});

dispatch_async(queue, ^{


for (int i = 0; i < 2; ++i) {

NSLog(@"3------%@",[NSThread currentThread]);

}

});

NSLog(@"asyncSerial---end");

}

执行结果

2016-03-13 19:07:35.119 多线程_Demo[3812:271447] asyncSerial---begin


2016-03-13 19:07:35.119 多线程_Demo[3812:271447] asyncSerial---end


2016-03-13 19:07:35.119 多线程_Demo[3812:271569] 1------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:07:35.120 多线程_Demo[3812:271569] 1------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:07:35.120 多线程_Demo[3812:271569] 2------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:07:35.120 多线程_Demo[3812:271569] 2------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:07:35.120 多线程_Demo[3812:271569] 3------<NSThread: 0x608000261600>{number = 3, name = (null)}


2016-03-13 19:07:35.121 多线程_Demo[3812:271569] 3------<NSThread: 0x608000261600>{number = 3, name = (null)}

从执行结果可以看出,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行。

主队列:

GCD自带的一种特殊的串行队列

所有放在主队列中的任务,都会放到主线程中执行

可使用dispatch_get_main_queue()获得主队列

主队列+同步执行

在主线程中调用,互等卡死。这是因为我们在主线程中执行任务,就把任务放到了主队列中,也就是主线程的队列中,而同步执行有一个特点,就是对于任务是立即执行。那么当我们把任务放到主队列之后,它就会立即执行。但是主线程现在正在执行当前方法,所以任务要等到当前方法执行完毕之后才能执行。而当前方法又要等到任务执行完成之后才能往下执行,就出现互相等待的卡死现象。

如果是在非主线程中调用,则不会出现卡死现象,在主线程中任务一个接一个执行

主队列+异步执行

只在主线程中执行任务,执行完一个任务,再执行下一个任务

GCD线程之间的通讯

在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

+(void)backToMainQueue{

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

sleep(3);

for (int i = 0; i < 2; ++i) {

NSLog(@"2------%@",[NSThread currentThread]);

}

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"刷新UI界面");

});

});

}

GCD的其他方法

GCD的延时执行方法 dispatch_after

当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

+(void)afterTime{

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

// 2秒后异步执行这里的代码...

NSLog(@"run-----");

});

}

GCD的一次性代码(只执行一次) dispatch_once

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。

+(void)operationOnce{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

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

NSLog(@"once----");

});

}

GCD的快速迭代方法 dispatch_apply

通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。比如说遍历0~5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。

+(void)anyApply{

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {

NSLog(@"%zd------%@",index, [NSThread currentThread]);

});

}

执行结果

2016-03-13 20:21:32.175 多线程_Demo[4395:304488] 1------<NSThread: 0x618000068f40>{number = 1, name = main}

2016-03-13 20:21:32.175 多线程_Demo[4395:304577] 0------<NSThread: 0x60000006ac00>{number = 6, name = (null)}

2016-03-13 20:21:32.176 多线程_Demo[4395:305754] 2------<NSThread: 0x608000076a80>{number = 7, name = (null)}

2016-03-13 20:21:32.176 多线程_Demo[4395:305755] 4------<NSThread: 0x61000006a540>{number = 9, name = (null)}

2016-03-13 20:21:32.176 多线程_Demo[4395:305753] 3------<NSThread: 0x618000079540>{number = 8, name = (null)}

2016-03-13 20:21:32.176 多线程_Demo[4395:305756] 5------<NSThread: 0x60000006b180>{number = 10, name = (null)}

GCD的栅栏方法 dispatch_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

+(void)barrier{

dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

sleep(2);

NSLog(@"----1-----%@", [NSThread currentThread]);

});

dispatch_async(queue, ^{

sleep(1);

NSLog(@"----2-----%@", [NSThread currentThread]);

});

dispatch_barrier_async(queue, ^{

NSLog(@"----barrier-----%@", [NSThread currentThread]);

});

dispatch_async(queue, ^{

sleep(2);

NSLog(@"----3-----%@", [NSThread currentThread]);

});

dispatch_async(queue, ^{

NSLog(@"----4-----%@", [NSThread currentThread]);

});

}

执行结果

2016-03-13 20:19:59.033 多线程_Demo[4395:304592] ----2-----<NSThread: 0x608000076bc0>{number = 3, name = (null)}

2016-03-13 20:20:00.034 多线程_Demo[4395:304574] ----1-----<NSThread: 0x610000066ac0>{number = 4, name = (null)}

2016-03-13 20:20:00.034 多线程_Demo[4395:304574] ----barrier-----<NSThread: 0x610000066ac0>{number = 4, name = (null)}

2016-03-13 20:20:00.034 多线程_Demo[4395:304575] ----4-----<NSThread: 0x600000068a00>{number = 5, name = (null)}

2016-03-13 20:20:02.035 多线程_Demo[4395:304574] ----3-----<NSThread: 0x610000066ac0>{number = 4, name = (null)}

GCD的队列组 dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。

  • 我们可以先把任务放到队列中,然后将队列放入队列组中。
  • 调用队列组的dispatch_group_notify回到主线程执行操作。

+(void)group{

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 执行1个耗时的异步操作

NSLog(@"正在执行第一个下载任务");

sleep(3);

NSLog(@"第一个下载任务执行完成");

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 执行1个耗时的异步操作

NSLog(@"正在执行第二个下载任务");

sleep(1);

NSLog(@"第二个下载任务执行完成");

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

// 等前面的异步操作都执行完毕后,回到主线程...

NSLog(@"下载任务执行完成");

});

}

执行结果

2016-03-13 20:18:07.112 多线程_Demo[4352:303197] 正在执行第二个下载任务

2016-03-13 20:18:07.112 多线程_Demo[4352:303211] 正在执行第一个下载任务

2016-03-13 20:18:08.115 多线程_Demo[4352:303197] 第二个下载任务执行完成

2016-03-13 20:18:10.115 多线程_Demo[4352:303211] 第一个下载任务执行完成

2016-03-13 20:18:10.115 多线程_Demo[4352:303154] 下载任务执行完成

多线程开发之GCD的更多相关文章

  1. iOS多线程开发之GCD(中篇)

    前文回顾: 上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue:第二 ...

  2. iOS多线程开发之GCD(死锁篇)

    上篇和中篇讲解了什么是GCD,如何使用GCD,这篇文章将讲解使用GCD中将遇到的死锁问题.有兴趣的朋友可以回顾<iOS多线程开发之GCD(上篇)>和<iOS多线程开发之GCD(中篇) ...

  3. iOS多线程开发之GCD(中级篇)

    前文回顾: 上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue:第二 ...

  4. iOS多线程开发之GCD(基础篇)

    总纲: GCD基本概念 GCD如何实现 GCD如何使用 队列和任务组合 一.GCD基本概念 GCD 全称Grand Central Dispatch(大中枢队列调度),是一套低层API,提供了⼀种新的 ...

  5. iOS多线程开发之NSOperation - 快上车,没时间解释了!

    一.什么是NSOperation? NSOperation是苹果提供的一套多线程解决方案.实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象.代码可读性更高.可控性更强 ...

  6. iOS多线程开发之NSOperation

    一.什么是NSOperation? NSOperation是苹果提供的一套多线程解决方案.实际上NSOperation是基于GCD更高一层的封装,但是比GCD更加的面向对象.代码可读性更高.可控性更强 ...

  7. iOS 开发之 GCD 不同场景使用

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

  8. iOS 开发之 GCD 基础

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

  9. Java 多线程开发之 Callable 与线程池

    前言 我们常见的创建线程的方式有 2 种:继承 Thread 和 实现 Runnable 接口. 其实,在 JDK 中还提供了另外 2 种 API 让开发者使用. 二.简单介绍 2.1 Callabl ...

随机推荐

  1. [Struts]Token 使用及原理

      Struts Token 使用 1,先在一个Action中,调用saveToken(HttpServletRequest request)方法.然后转向带有表单的JSP页面. 2,在JSP页面提交 ...

  2. SciPy 图像处理

    章节 SciPy 介绍 SciPy 安装 SciPy 基础功能 SciPy 特殊函数 SciPy k均值聚类 SciPy 常量 SciPy fftpack(傅里叶变换) SciPy 积分 SciPy ...

  3. 2018--Linux面试题

    1.企业场景面试题:buffer与Cache的区别. 2.企业场景面试题:redhat与CentOS的区别. 3.企业场景面试题:  描述RAID 0 1 5 10的特点. 4.企业场景面试题:32位 ...

  4. 获取QQ群中的所有群友QQ

    package com.jm.mail.tools; import java.io.BufferedReader; import java.io.IOException; import java.io ...

  5. arm linux 移植 ffmpeg 库 + x264

    背景 Ffmpeg 中带有h264的解码,没有编码,需要添加x264.libx264是一个自由的H.264编码库,是x264项目的一部分,使用广泛,ffmpeg的H.264实现就是用的libx264. ...

  6. 论文阅读:Blink-Fast Connectivity Recovery Entirely in the Data Plane

    1.背景 在网络中,链路故障的发生在所难免,为了降低故障带来的影响,就需要重新路由,将数据传输到合适的链路上.当因为链路故障发生处的不同,也有不同的解决方法. AS(Autonomous System ...

  7. SQL statement ignored

    存储过程语句错误,字段或变量名可能拼错,导致存储过程无法执行. 解决办法:仔细检查存储过程里的变量,字段,语句等是否正确.

  8. 015.Oracle数据库,取本月月初,取本月月末

    /*取本月月初,取本月月末*/ SELECT trunc( SYSDATE, 'mm' ) AS 月初 , last_day(trunc(sysdate)) AS 月末 FROM dual; 修改如下 ...

  9. 三十二、CI框架之配置域名和设置默认登陆网站

    一.打开routes.php文件,将$route['default_controller'] = 'login'; 修改成我们需要的内容. 二.修改config.php中的base_url数据 三.L ...

  10. 5.Linux解决Device eth0 does not seem to be present

    Linux操作系统排除故障 导入vixualbox的虚拟机voa文件到另外一台电脑,需要检查如下信息 修改虚拟机软件网络设置 重启Linux操作系统 shutdown -h now reboot se ...