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就可以生成必要的线程并计划执行任务。

它具有以下优点:

    1. GCD可以将花费时间极其长的任务放到后台线程,可以改善应用的响应性能
    2. GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱
    3. GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力(后面会提供一个单例的medo)
    4. 等等

如下面的代码片段:


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 和线程之间的关系,如下图:

时间: 2024-12-26 01:13:55

iOS开发:深入理解GCD 第一篇的相关文章

IOS开发学习之路--第一篇--准备和说明

经过1个多月的对Objective-C的学习和了解,对其也有一定的认识,但是仅仅是了解,还需要今后的不断熟练和摸索才能掌握. 从现在写这个文章起,正式进入了学习IOS开发之路.之前看了很多人发表自己的论坛或者博客,觉得自己也该有这个东西,不仅可以充实学习内容,也是积累知识的过程.同时也可以总结学习过程中的各种方法. 长风破浪会有时,直挂云帆济沧海!每天进步一点点,生活乐趣大点点! 学习IOS开发,主要的学习资料: 1)OC基础,李明杰IOS开发基础视频(C和OC)经典 2)IOS开发技术,黑马2

斯坦福IOS开发第五课(第一部分)

转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/27706991 作者:小马 由于第五课的内容比较多,分两部分来写. 一 屏幕旋转基本操作 控制当前的view是否支持旋转,如果是,是支持哪些方向的,有四个方向,分别是home键在下,上,左右. 在当前的viewcontroller里,实现shouldAutorotateToInterfaceOrientation方法,告诉系统你支持的旋转方向,如下: - (BOOL)shouldA

iOS开发中的gcd多线程tips

iOS开发中的gcd多线程tips 我们经常用到的: dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 处理耗时操作的代码块 //通知主线程刷新 dispatch_async(dispatch_get_main_queue(), ^{ //回调或者说是通知主线程刷新 }); }); 其中main_queue是系统默认的串行队列,global_queue是系统默认的并行队列. 什么是串行队列(Serial)? 创建任意个数的串行队列,每个队

iOS开发之多线程——GCD介绍

iOS开发之多线程——GCD的介绍 一.简单介绍 1.GCD ( Grand Central Dispatch) 可以翻译为“中枢调度器”.纯C语言,并且提供了非常强大的函数. 2.GCD 有什么优势: GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自动利用更多地CPU 内核 (比如双核.四核) GCD会自动管理线程的生命周期 (创建线程.调度任务.销毁线程) 程序猿只需要告诉GCD想要执行设呢任务,不需要编写任何线程管理代码. 二.任务和队列 GCD中有两个核心概念 (1)任务: 执

iOS开发——完整项目实战OC篇&amp;百思不得姐第四天

iOS开发——完整项目实战OC篇&百思不得姐第四天 上午 一:自定义按钮使用九宫格布局 二:控件不能点击 三:获取用户点击了那个按钮 四:调整按钮内部控件的位置:主流->上下 五:不能直接使用self.navigationController中或者View中获取导航控制器 方法一: 方法二: 六:布局取整 1 // 总行数 2 3 // NSUInteger rows = sqaures.count / maxCols; 4 5 // if (sqaures.count % maxCols)

iOS开发SDWebImageOptions理解

iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { SDWebImageRetryFailed = 1 << 0, SDWebImageLowPriority = 1 << 1, SDWebImageCacheMemoryOnly = 1 << 2, SDWebImag

iOS开发:深入理解GCD 第二篇(dispatch_group、dispatch_barrier、基于线程安全的多读单写)

Dispatch Group在追加到Dispatch Queue中的多个任务处理完毕之后想执行结束处理,这种需求会经常出现.如果只是使用一个Serial Dispatch Queue(串行队列)时,只要将想执行的处理全部追加到该串行队列中并在最后追加结束处理即可,但是在使用Concurrent Queue 时,可能会同时使用多个Dispatch Queue时,源代码就会变得很复杂. 在这种情况下,就可以使用Dispatch Group. 1 2 3 4 5 6 7 8 9 10 11 12 13

iOS开发之多线程技术——NSOperation篇

本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解: 一.什么是NSOperation 二.我们为什么使用NSOperation 三.在实际开发中如何使用NSOperation 1.自定义NSOperation 2.NSOperation的基本使用 3.NSOperation实现线程间通信 1)利用代理进行消息传递 2)利用通知实现消息传递 3)利用block进行消息传递 四.与GCD比较 一.什么是NSOperation NSOperation是一个抽象的基类,表示一个独

iOS开发——网络使用技术OC篇&amp;网络爬虫-使用正则表达式抓取网络数据

网络爬虫-使用正则表达式抓取网络数据 关于网络数据抓取不仅仅在iOS开发中有,其他开发中也有,也叫网络爬虫,大致分为两种方式实现 1:正则表达 2:利用其他语言的工具包:java/Python 先来看看网络爬虫的基本原理: 一个通用的网络爬虫的框架如图所示: 网络爬虫的基本工作流程如下: 1.首先选取一部分精心挑选的种子URL: 2.将这些URL放入待抓取URL队列: 3.从待抓取URL队列中取出待抓取在URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中.