GCD基础知识

并行和并发

在英文世界里,「并行」和「并发」的区别比较清晰,「并行」对应parallelism,「并发」对应concurrency;但在中文世界里二者仅一字之差,两个概念非常容易弄混淆;

各种资料对「并行」和「并发」有各种各样的解释和比喻。我比较喜欢的一种是播客节目内核恐慌中的主播Rio的描述,大概意思是:

「并发」和「并行」是一种计算模型,使得计算机能够在同一时间处理多个任务;「并发」表示逻辑概念上的「同时」,「并行」表示物理概念上的「同时」。

简单来说,若说两个任务A和B并发执行,则表示任务A和任务B在同一时间段里被执行(更多的可能是二者交替执行);若说任务A和B并行执行,则表示任务A和任务B在同时被执行(这要求计算机有多个运算器);

一句话:并行要求并发,但并发并不能保证并行。

P.S:在《GCD深入理解(1)》中有更详细的图文解释,参考这里

Dispatch Queues介绍

Dispatch Queues是GCD处理异步任务和并发任务的关键载体,简而言之,在GCD中,将task放入某个Dispatch Queue中,然后等待系统去处理之。

Dispatch queue是object-like structure,也就是说Dispatch queue在Objective-C中不是类结构,而是类类结构。dispatch queue对task的管理都遵循FIFO。GCD提供了一些公共的dispatch queue,但是用户也可以自定义一些dispatch queue;iOS对dispatch queue做了归类,分为三类:

  • Serial Dispatch Queue
  • Concurrent Dispatch Queue
  • Main Dispatch Queue

Serial Dispatch Queue

顾名思义,serial dispatch queue中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。即每次从queue中取出一个task进行处理;用户可以根据需要创建任意多的serial dispatch queue,serial dispatch queue彼此之间是并发的;

创建serial dispatch queue使用dispatch_queue_create方法,指定其第二个参数为DISPATCH_QUEUE_SERIAL(即NULL)即可:

dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MySerialQueue",DISPATCH_QUEUE_SERIAL);

注意:如果不算“Main Dispatch Queue”,系统中不存在所谓的global serial dispatch queue。

P.S:main dispatch queue其实也算serial dispatch queue,后文有述。

Concurrent Dispatch Queue

相对于Serial Dispatch Queue,Concurrent Dispatch Queue一次性并发执行一个或者多个task;和Serial Dispatch Queue不同,系统提供了四个global concurrent queue,使用dispatch_get_global_queue函数就可以获取这些global concurrent queue;

和Serial Dispatch Queue一样,用户也可以根据需要自己定义concurrent queue;创建concurrent dispatch queue也使用dispatch_queue_create方法,所不同的是需要指定其第二个参数为DISPATCH_QUEUE_CONCURRENT即可:

dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

P.S:根据我的理解,对于concurrent queue,其管理的task可能在多个不同thread上执行,至于dispatch queue管理多少个thread是未知的,这要视系统资源而定,用户无需为此烦扰。
创建concurrent queue也是使用dispatch_queue_create方法,指定第二个参数为DISPATCH_QUEUE_CONCURRENT。

Main Dispatch Queue

关于Main Dispatch Queue,《Concurrency Programming Guide》(Apple官方文档)的描述如下:

The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread.

根据我的理解,application的主要任务(譬如UI管理之类的)都在main dispatch queue中完成;根据文档的描述,main dispatch queue中的task都在一个thread中运行,即application’s main thread(thread 1)。

P.S:所以,如果想要更新UI,则必须在main dispatch queue中处理,获取main dispatch queue也很容易,调用dispatch_get_main_queue()函数即可。

关于Dispatch Queues的一些误解

在学习GCD过程中,我一路上有许多关于dispatch的错误理解,如下是总结:

1.不存在所谓的「同步队列」和「异步队列」

「同步」或「异步」描述的是task与其上下文之间的关系,所以,笔者觉得「同步队列」和「异步队列」对于Objective-C的GCD而言是不靠谱的概念;

2.Serial Dispatch Queue上的tasks并非只在同一个thread上执行

吾尝以为serial queue上的tasks都是在同一个thread上运行,后来明白了不是这样的,对于那些同步请求的任务,譬如使用dispatch_sync函数添加到serial dispatch queue中的任务,其运行的task往往与所在的上下文是同一个thread;对于那些异步请求的任务,譬如使用dispatch_async函数添加到serial dispatch queue中的任务,其运行的task往往是另一个的thread。举例说明:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t aSerialQueue = dispatch_queue_create("haha", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(aSerialQueue, ^{
        // block 1
        NSLog(@"current 1: %@", [NSThread currentThread]);
    });

    dispatch_async(aSerialQueue, ^{
        // block 2
        NSLog(@"current 2: %@", [NSThread currentThread]);
    });
}

/*
执行结果:
current 1: <NSThread: 0x7f8f397152f0>{number = 1, name = main}
current 2: <NSThread: 0x7f8f39464db0>{number = 2, name = (null)}
*/

block 1和block 2都由同一个serial dispatch queue管理,但它们的执行线程显然不同,前者的执行线程是thread 1,后者的执行线程是thread 2。

3.dispatch queue和thread并不存在一对一或者一对多的关系。

通过设置断点等测试手段可以知道可能多个dispatch queue共用一个thread,也可能一个dispatch queue中的tasks在多个不同threads上执行。

总之,根据我的理解,thread和dispatch queue之间没有从属关系。

dispatch_sync和dispatch_async

dispatch_sync和dispatch_async介绍

在GCD中,dispatch_sync和dispatch_async是两个函数,前者用于派发同步任务,后者用于派发异步任务,二者使用格式如下:

// dispatch task synchronously
dispatch_sync(someQueue1, ^{
    // do something 1
});
// do something 2
// dispatch task asynchronously
dispatch_async(someQueue2, ^{
    // do something 3
});
// do something 4

对于派发同步任务,do something 2一定会在do something 1完成之后执行,即所谓的“同步”。当执行到dispatch_sync(…)时,其上下文被阻塞,直到dispatch_sync派发的block被执行完毕;并且,根据笔者的理解,dispatch_sync派发的block的执行线程和dispatch_sync上下文线程是同一个线程

P.S:“dispatch_sync派发的block的执行线程和dispatch_sync上下文线程是同一个线程”这个说法还没有找到权威的、直接明了的佐证。

对于派发异步任务,do something 4会立即执行,而不会等到do something 3执行完,即所谓“异步”。当执行到dispatch_async(…)时,其上下文不被阻塞,继续运行;
根据笔者的理解,do something 3do something 4往往在不在同一个Thread中被执行,即dispatch_async派发的block的执行线程和dispatch_async上下文线程不是同一个线程

来看一个示例,如下有一段代码:

// 1. create a serial dispatch queue
dispatch_queue_t serial_queue=
dispatch_queue_create("com.zhangbuhuai.test", DISPATCH_QUEUE_SERIAL);    // Thread 1

// 2. add tasks to serial dispatch queue
// 1) add a task synchronously
dispatch_sync(serial_queue, ^{
    sleep(3);                  // 休眠3秒
    NSLog(@"task 1");        // Thread 1
});
// 2) add a task synchronously too
dispatch_sync(serial_queue, ^{
    NSLog(@"task 2");        // Thread 1
});
// 3) add a task asynchronously
dispatch_async(serial_queue, ^{
    NSLog(@"task 3");        // Thread x  (x != 1)
});
// 4) add a task asynchronously too
dispatch_async(serial_queue, ^{
    NSLog(@"task 4");        // Thread x  (x != 1)
});

NSLog(@"test end");            // Thread 1

假设建立serial_queue所在的上下文运行的thread为Thread 1,则测试结果很可能是NSLog(@"task 1");NSLog(@"task 2");也都在Thread 1中执行,而NSLog(@"task 3");NSLog(@"task 4");在别的Thread中执行。

执行结果:

task 1
task 2
test end
task 3
task 4

根据结果我们知道,对于serial dispatch queue中的tasks,无论是同步派发还是异步派发,其执行顺序都遵循FIFO;同样,这个示例也可以直观阐述dispatch_sync和dispatch_async的不同效果。

dispatch_sync和dispatch_async的使用时机

在大多数时候,dispatch_sync和dispatch_async的使用时机非常清晰的:

  • 如果派发的task耗时长,不想让上下文线程被阻塞就用dispatch_async;
  • 如果要处理的代码比较短,想要实现代码保护(线程安全),选用dispatch_sync;

P.S:关于dispatch_sync与线程同步(代码保护)之间的关系,以后会专门阐述。

但有些时候,使用dispatch_sync或者dispatch_async都可以的情况(譬如实现setter)下,就不是那么好选择了。

在《Effective Objective-C 2.0》Item 41(中文版P169)中看到非常重要的一句话:

…,因为在执行异步派发时,需要拷贝块。

我对这句话的理解是:

  • 执行同步派发(dispatch_sync)时,是不需要拷贝block的,这是因为dispatch_sync中所派发的task往往和当前上下文所处同一个Thread;
  • 执行异步派发(dispatch_async)时,需要拷贝block,这是因为dispatch_async中所派发的task往往和当前上下文不同于一个Thread;

所以,当选择dispatch_sync或者dispatch_async都可以的情况下,站在效率的角度,如果拷贝block的时间成本过高,则使用dispatch_sync;如果拷贝block的时间成本远低于执行block的时间成本,则使用dispatch_async;

P.S:《Effective Objective-C 2.0》上的这句话“…,因为在执行异步派发时,需要拷贝块”在某种程度上佐证了上文提到的两个说法:

  1. dispatch_sync派发的block的执行线程和dispatch_sync上下文线程是同一个线程
  2. dispatch_async派发的block的执行线程和dispatch_async上下文线程不是同一个线程

参考资料:

    1. 《Concurrency Programming Guide》(Apple官方文档)
    2. 《Effective Objective-C 2.0》
时间: 2024-10-12 10:26:58

GCD基础知识的相关文章

iOS 多线程知识总结 GCD基础知识

进程的基本概念: 1.每一个进程都是一个一个应用程序,都有独立的内存空间,一般来说一个应用程序存在一个进程存在一个进程,但也有多个进程的情况. 2.同一个进程中的线程共享内存中内存中资源. 多线程的基本概念: 1.每一个程序都有一个主线程,程序启动时创建(调用main函数来启动) 2.主线程的生命周期是和其他应用程序绑定的,程序退出时,主线程也就停止了. 3.多线程技术表示,一个应用程序有多个线程,使用多线程能提高CPU的使用效率,防止线程阻塞 4.任何有可能阻塞主线程的任务不要在主线程中执行(

活到老学到老:iOS开发中的基础知识(一)

本文参考 标哥的博客:宝库iOS开发笔试题 进行学习整理.与其说是看面试题,不如说是对自己知识的巩固.工欲善其事必先利其器,基础知识不牢固可能会导致编程中的一些注意不到的问题.总之一句话:活到老,学到老. 1.数组中的元素去重问题. //重复元素 NSArray *array = [NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"1"

Java基础知识【下】( 转载)

http://blog.csdn.net/silentbalanceyh/article/details/4608360 (最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没有考虑到会坚持往后边写,这次应该是更新该内容.而且很讨厌写基础的东西,内容比较琐碎,而且整理起来总会很多,有可能会打散成两个章节,但是我不保证,有可能一个章节就写完了,所以有时候希望基础的很多内容还是读者自己去看看,我基本保证把基础的内容全部都写出来,见谅.这一个章节写了过后我会把前边那个关于基础类

iOS开发多线程基础知识

--------------------------多线程概念基础------- 进程:正在运行的程序 内存:每个进程所占的存储空间 线程:1个进程要像执行任务,必须得有线程,线程是进程的基本执行单元, 线程的串行: ·1个线程中人物的执行是串行的 ·0同一个时间内,1个线程只能执行1个任务 0·线程是进程的一条执行路径 --------多线程 ·一个进程中可以开启多条线程,每条线程可以并行(同时)同时执行不同的任务 ·进程->车间 线程->车间工人 线程的并行: ·进程内多个线程同时执行,可

IOS-OC的基础知识

IOS学习之路--OC的基础知识 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch框架 3.Xcode使用 -------------------- CocoaTouch Media Core Services Core OS -------------------- System Framework OC的类声明,定义域 OC关键字定义为  @class O-C特有的语句for(..in ..)迭代循环,其他的条件和循环语句和c

MySQL数据库基础知识

day02 MySQL数据库基础知识 一.基础知识概述: 基础决定你这门课程的学习成败!只有学习好这些基础知识以后,你才能真正的运用自如.才能够对数据库有更深入的了解,道路才会越走越远. 二.基础知识: 1.数据库(database):数据库就好比是一个物理的文档柜,一个容器,把我们整理好的数据表等等归纳起来. 创建数据库命令:        create database 数据库名; 2.查看数据库         show databases; 3.打开指定的数据库         use 

linux入门基础知识及简单命令介绍

linux入门基础知识介绍 1.计算机硬件组成介绍 计算机主要由cpu(运算器.控制器),内存,I/O,外部存储等构成. cpu主要是用来对二进制数据进行运算操作,它从内存中取出数据,然后进行相应的运算操作.不能从硬盘中直接取数据. 内存从外部存储中取出数据供cpu运存.内存的最小单位是字节(byte) 备注:由于32的cpu逻辑寻址能力最大为32内存单元.因此32位cpu可以访问的最大内存空间为:4GB,算法如下: 2^32=2^10*2^10*2^10*2^2 =1024*1024*1024

BroadcastReceive基础知识总结

BroadcastReceive基础知识总结 1.BroadcastReceive简介 BroadcastReceive也就是"广播接收者"的意思,顾名思义,就是用来接收来自系统和应用中的广播 在Android系统中,广播体现在方方面面,例如当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能,当网络状态改变时,系统会产生一条广播,接收到这条广播,就能及时的做出提示和保存数据等操作,当电池的电量改变的时候,系统会产生一条广播,接收到这条广播就能在电量低的时候告知用户

基础知识--:before伪元素和:after伪元素

http://book.51cto.com/art/201108/285688.htm 3.7  替换指定位置 大家都知道before和after是前.后的意思.但是奇怪的是,CSS中的:before伪元素和:after伪元素是为源文档中不存在的内容设置样式的. 没有内容怎么设置样式呢?别急!它们有一个content属性,一起使用就可以为某个选择器前.后的内容设置样式了. 下面就来了解一下:before伪元素和:after伪元素的用法. 视频教学:光盘/视频/3/3.7  替换指定位置.avi