iOS开发:深入理解GCD 第一篇
最近把其他书籍都放下了,主要是在研究GCD。如果是为了工作,以我以前所学的GCD、NSOperation等知识已经足够用了,但学习并不仅仅知识满足于用它,要知其然、并且知其所以然,这样才可以不断的提高自身技术水平。
本文主要参考http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 和 《iOS与OS X 多线程和内存管理》,以及其他一些杂七杂八的书籍或者博客。
GCD已经面世很久了,基于GCD面向对象的多线程技术NSOperation也出现很久了,但并不是所有人都明了GCD主要内容,并发一直很棘手(虽然AFNetworking框架在一定程度上避免了很多时候我们在程序内赤裸裸的写多线程代码,但我想任何一个有想法的程序员都会深入理解并发编程,并将之掌握的),它们就像一组尖锐的棱角戳进 Objective-C 的平滑世界。
在这里,我将会四个篇幅来梳理自己所知、所理解的GCD。
第一、二篇主要是解释GCD是什么、能做什么,会提供我所编写好的一些代码片段,必要时,会有demo。
第三篇和第三篇主要是学习一些高级GCD提供的高级函数,如果时间充足,我会尽量以文字+代码+demo展示。
如果你对GCD和Block(代码块)完全陌生,请先看这篇文章:http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial 《iOS上GCD和多线程的入门教程》
进程:也就是一个正在运行的应用程序。
线程:进程中的某一条完整的执行路径。一个进程可以有多个线程,至少有一个线程,即主线程。在iOS开发中,所有涉及UI界面的,必须在主线程中更新。
什么是GCD?
苹果官方给出的解释:GCD是异步执行任务的技术之一。一般将应用程序中记述的线程管理代码在系统集中实现,开发者只需要定义想执行的任务并追加到
适当的Dispatch Queue中,GCD就可以生成必要的线程并计划执行任务。
它具有以下优点:
- GCD可以将花费时间极其长的任务放到后台线程,可以改善应用的响应性能
- GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱
- GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力(后面会提供一个单例的medo)
- 等等
如下面的代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
dispatch_queue_t queue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ /** 放一些极其耗时间的任务在此执行 */ dispatch_async(dispatch_get_main_queue(), ^{ /** 耗时任务完成,拿到资源,更新UI 更新UI只可以在主线程中更新 */ }); }); |
我创建了一个并行队列queue,并异步执行耗时操作,当耗时操作执行完毕,我拿到其中的资源回到主线程来更新相应的UI,在这个Block代码块之外,主线程并不会被耗时任务所堵塞,可以流畅的处理其他事情。
GCD的一些术语
要理解 GCD ,要先熟悉与线程和并发相关的几个概念。
串行(Serial)与 并发(Concurrent)
任务串行,意味着在同一时间,有且只有一个任务被执行,即一个任务执行完毕之后再执行下一个任务。
任务并发,意味着在同一时间,有多个任务被执行。
同步(Synchronous)与 异步 (Asynchronous)
同步,意味着在当前线程中执行任务,不具备开启新的线程的能力。
异步,在新的线程中执行任务,具备开启新的线程的能力。
在 GCD 中,这些术语描述当一个函数相对于另一个任务完成,此任务是该函数要求 GCD 执行的。一个同步函数只在完成了它预定的任务后才返回。
一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。
临界区(Critical Section)
就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它很可能会变质(它的值不再可信)。
死锁(Deadlock)
停止等待事情的线程会导致多个线程相互维持等待,即死锁。
两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。
代码片段:
1
2
3
4
5
6
7
8
9
10
|
- ( void )viewDidLoad { [ super viewDidLoad]; dispatch_sync(dispatch_get_main_queue(), ^{ NSLog (@ "111111" ); }); NSLog (@ "222222" ); } |
执行上面的代码,你会发现没有任何打印,这个时候就是发生了死锁,我们禁止在主队列(iOS开发中,主队列是串行队列)中,在同步使用主队列执行任务,同理,禁止在同一个同步串行队列中,再使用该串行队列同步的执行任务,因为这样会造成死锁。
代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
- ( void )viewDidLoad { [ super viewDidLoad]; dispatch_queue_t queue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog (@ "111111" ); dispatch_sync(queue, ^{ NSLog (@ "22222" ); }); NSLog (@ "3333333" ); }); NSLog (@ "44444444" ); } |
会发现,只是打印了一次,然后就造成了死锁。
线程安全(Thread Safe)
线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。
上下文切换(Context Switch)
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。
并发与并行
并行要求并发,但并发不能保证并行,就计算机操作系统来说,开启线程是很耗性能的,也就是说,事实上,在某次并行处理任务中,开启的线程是有上限的,如果上限为2,即每次开启的新线程为2,那么是有可能出现并发却不并行的情况。
并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉,如下图所示:
队列(Queue)
苹果官方对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
这句话的源码如下:
1
2
3
4
5
6
7
|
dispatch_async(queue, ^{ /** * 想要执行的任务 */ }); |
该源码使用Block语法“定义想执行的任务”,通过dispatch_async函数“追加”赋值在变量queue的"Dispatch Queue中"。仅仅是这样,就可以使得指定的Block在另一线程中执行。
GCD 提供有 dispatch queue 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO (先进先出)顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。
Dispatch Queue是什么呢?是执行处理的等待队列,程序员通过dispatch_async等API,在Block语法中记述想要执行的处理,并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序进行处理。
所有的调度队列(dispatch queue)自身都是线程安全的,你能从多个线程并行的访问它们。 GCD 的优点是显而易见的,即当你了解了调度队列如何为你自己代码的不同部分提供线程安全。关于这一点的关键是选择正确类型的调度队列和正确的调度函数来提交你的工作。
另外,在执行处理时,存在本文前面提到的两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是Concurrent Dispatch Queue。
串行队列(Serial Dispatch Queue )
这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
如下代码,当调用serialPrintNumber方法时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#import "ViewController.h" @interface ViewController () @property ( nonatomic , strong) dispatch_queue_t serialQueue; @property ( nonatomic , strong) dispatch_queue_t concurrentQueue; @end @implementation ViewController - ( void )viewDidLoad { [ super viewDidLoad]; self .serialQueue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_SERIAL); self .concurrentQueue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_CONCURRENT); for ( int i = 0; i < 100; i++) { [ self serialPrintNumber:i]; } } /** * 异步串行队列 * */ - ( void )serialPrintNumber:( int )number { dispatch_async( self .serialQueue, ^{ NSLog (@ "%d %@" ,number, [ NSThread currentThread]); }); } /** * 异步并行队列 * */ - ( void )concurrentPrintNumber:( int )number { dispatch_async( self .concurrentQueue, ^{ NSLog (@ "%d %@" ,number, [ NSThread currentThread]); }); } @end |
可以看到这样的打印:
开辟了一个新的子线程,任务是按顺序执行的。先进先出顺序执行的。因为要等待前一个任务处理结束,即同一时间,只能处理一个任务,才可以开始处理下一任务。
并发队列(Concurrent Dispatch Queue)
在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但这就是全部的保证了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。再说一遍,这完全取决于 GCD
代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#import "ViewController.h" @interface ViewController () @property ( nonatomic , strong) dispatch_queue_t serialQueue; @property ( nonatomic , strong) dispatch_queue_t concurrentQueue; @end @implementation ViewController - ( void )viewDidLoad { [ super viewDidLoad]; self .serialQueue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_SERIAL); self .concurrentQueue = dispatch_queue_create( "cn.chutong.www" , DISPATCH_QUEUE_CONCURRENT); for ( int i = 0; i < 100; i++) { [ self concurrentPrintNumber:i]; } } /** * 异步串行队列 * */ - ( void )serialPrintNumber:( int )number { dispatch_async( self .serialQueue, ^{ NSLog (@ "%d %@" ,number, [ NSThread currentThread]); }); } /** * 异步并行队列 * */ - ( void )concurrentPrintNumber:( int )number { dispatch_async( self .concurrentQueue, ^{ NSLog (@ "%d %@" ,number, [ NSThread currentThread]); }); } @end |
打印:
可以看到,不再是顺序执行任务,开了多个线程。至此,我们可以清楚的知道,所谓“并行执行”,就是使用多个线程同时处理多个任务,因为完成任务所需要消耗的时间不同,所以完成任务的最终时间不同。
Serial Dispatch Queue 与 Concurrent Dispatch Queue 和线程之间的关系,如下图:
iOS开发:深入理解GCD 第一篇的更多相关文章
- 解析iOS开发中的FirstResponder第一响应对象
1. UIResonder 对于C#里所有的控件(例如TextBox),都继承于Control类.而Control类的继承关系如下: 代码如下: System.Object System.Marsha ...
- iOS开发SDWebImageOptions理解
iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUIn ...
- iOS开发:深入理解GCD 第二篇(dispatch_group、dispatch_barrier、基于线程安全的多读单写)
Dispatch Group在追加到Dispatch Queue中的多个任务处理完毕之后想执行结束处理,这种需求会经常出现.如果只是使用一个Serial Dispatch Queue(串行队列)时,只 ...
- iOS开发——网络使用技术OC篇&网络爬虫-使用正则表达式抓取网络数据
网络爬虫-使用正则表达式抓取网络数据 关于网络数据抓取不仅仅在iOS开发中有,其他开发中也有,也叫网络爬虫,大致分为两种方式实现 1:正则表达 2:利用其他语言的工具包:java/Python 先来看 ...
- iOS开发之多线程技术——NSOperation篇
本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解: 一.什么是NSOperation 二.我们为什么使用NSOperation 三.在实际开发中如何使用NSOperation ...
- iOS开发——高级技术精选OC篇&Runtime之字典转模型实战
Runtime之字典转模型实战 如果您还不知道什么是runtime,那么请先看看这几篇文章: http://www.cnblogs.com/iCocos/p/4734687.html http://w ...
- ios开发——常用经典算法OC篇&冒泡/快速
冒泡排序与快速排序 1.序言 ios开发中涉及到算法的地方还真不多,除非你的应用程序真的非常大,或者你想你的应用程序性能非常好才会去想到关于算法方面的性能优化,而在ios开发中真的能用得到的也就是关于 ...
- ios开发——实用技术OC-Swift篇&本地通知与远程通知详解
本地通知与远程通知详解 一:本地通知 Local Notification的作用 Local Notification(本地通知) :是根据本机状态做出的通知行为,因此,凡是仅需依赖本机状态即可判 ...
- iOS开发 - Swift使用GCD实现计时器功能
前言 开发中,经常会用到定时执行网络请求.倒计时.计时器等功能,本篇文章介绍在iOS开发中,Swift怎样使用GCD实现这些功能. 执行一次 下面的代码将会在5秒后执行,且只执行一次. let tim ...
随机推荐
- CAEmitterLayer实现粒子效果
在iOS 5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayer.CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果. CAEm ...
- Java 8十个lambda表达式案例
1. 实现Runnable线程案例 使用() -> {} 替代匿名类: //Before Java 8: new Thread(new Runnable() { @Override public ...
- Menu( 菜单)
一. 加载方式菜单组件通常用于快捷菜单,在加载方式上,通过 class 或 JS 进行设置为菜单组件.然后,再通过 JS 事件部分再响应.//class 加载方式<div id="bo ...
- input事件以及中文输入法的处理
在项目的开发过程中,相信大家都处理过监听用户输入的事情,一般我们会用到onkeyup.onkeydown.onkeypress.onchange.oninput事件,虽然都很熟悉了,但是还是有必要巩固 ...
- include子页面传递过来的参数传递到后台
在页面上可以使用 ${param.moduleId}来获取 在判断中也可以使用${param.moduleId == "test" ? "1":"2& ...
- java中的变量
变量就是命名的内存空间 1.声明和赋值方式:数据类型 变量名 = 值: 数据类型即划分的内存空间,变量名即划分出的内存空间的名 2.变量必须先声明才能使用,不能使用一个没有经过预先声明的变量:没有 ...
- canvas.toDataURL(type, args)
canvas.toDataURL(type, args)该方法能够将canvas转换为图像,图像是基于Base64编码的.如果不指定两个参数,无参数调用该方法,转换的图像格式为png格式 •type: ...
- Asp.Net WebAPI 通过HttpContextBase获取请求参数
WEBAPI中的Request是HttpRequestMessage类型,不能像Web传统那样有querystring和from 方法接收参数,而传统的HttpReqest的基类是HttpReqest ...
- traditional:true
- pendingIntent初步_什么是pendingIntent
pendingIntent字面意义:等待的,未决定的Intent. 要得到一个pendingIntent对象,使用方法类的静态方法 通过getActivity(Context context, int ...