最近把其他书籍都放下了,主要是在研究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 和线程之间的关系,如下图:

朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。

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

  1. 解析iOS开发中的FirstResponder第一响应对象

    1. UIResonder 对于C#里所有的控件(例如TextBox),都继承于Control类.而Control类的继承关系如下: 代码如下: System.Object System.Marsha ...

  2. iOS开发SDWebImageOptions理解

    iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUIn ...

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

    Dispatch Group在追加到Dispatch Queue中的多个任务处理完毕之后想执行结束处理,这种需求会经常出现.如果只是使用一个Serial Dispatch Queue(串行队列)时,只 ...

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

    网络爬虫-使用正则表达式抓取网络数据 关于网络数据抓取不仅仅在iOS开发中有,其他开发中也有,也叫网络爬虫,大致分为两种方式实现 1:正则表达 2:利用其他语言的工具包:java/Python 先来看 ...

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

    本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解: 一.什么是NSOperation 二.我们为什么使用NSOperation 三.在实际开发中如何使用NSOperation ...

  6. iOS开发——高级技术精选OC篇&Runtime之字典转模型实战

    Runtime之字典转模型实战 如果您还不知道什么是runtime,那么请先看看这几篇文章: http://www.cnblogs.com/iCocos/p/4734687.html http://w ...

  7. ios开发——常用经典算法OC篇&冒泡/快速

    冒泡排序与快速排序 1.序言 ios开发中涉及到算法的地方还真不多,除非你的应用程序真的非常大,或者你想你的应用程序性能非常好才会去想到关于算法方面的性能优化,而在ios开发中真的能用得到的也就是关于 ...

  8. ios开发——实用技术OC-Swift篇&本地通知与远程通知详解

    本地通知与远程通知详解 一:本地通知   Local Notification的作用 Local Notification(本地通知) :是根据本机状态做出的通知行为,因此,凡是仅需依赖本机状态即可判 ...

  9. iOS开发 - Swift使用GCD实现计时器功能

    前言 开发中,经常会用到定时执行网络请求.倒计时.计时器等功能,本篇文章介绍在iOS开发中,Swift怎样使用GCD实现这些功能. 执行一次 下面的代码将会在5秒后执行,且只执行一次. let tim ...

随机推荐

  1. JavaScript经典魔力代码

    是什么使得JavaScript不同于其他程序设计语言,在浏览器修饰方面表现出其优异的特性?毫无疑问,JavaScript在Web应用领域受到的好评,既源于它自身灵活的动态特性,也源于浏览器对它充分的支 ...

  2. Android模块化编程之引用本地的aar

    随着项目越来越多,代码的复用就变得异常重要,这时候就要进行模块化编程,就是把一些通用的组件或者类库做成单独的模块,其他项目直接进行引用就好.针对Android开发最常见的就是Android Libra ...

  3. Catel帮助手册-Catel.Core(6):日志管理

    1,简介      从2.2版本开始,Catel使用了一个自定义的日志系统,这种方式,针对log4net的引用可以被移除,做这个修改主要是为了不强迫用户使用log4net,同时,log4net看起来很 ...

  4. Playground

    题意 :求被两点分割的凸包面积的较小值    题意已经给出顺时针啦 就是求以某一个点 和其他所有相邻点组成三角形的面积,然后sum存和求两点的时候就求出那两点的之间所有三角形的和再减掉0点和那两点的面 ...

  5. uCgui和emWin的区别

              在国内做嵌入式系统的,开始入门OS的时候,大家应该都会选择uC/OS,为什么?因为代码开源且资料众多嘛.由于uC/OS的原因大家也一定接触了uC/GUI的嵌入式图形软件库.其实uC ...

  6. phpcms v9升级后台无法上传缩略图的原因分析

    phpcms V9 是目前国内使用人数最多的一款开源免费的CMS系统,正是由于他的免费性,开源性,以及其自身的功能性比较强大,所以倍受许多站长朋友们的亲来,以及许多的公司的喜欢.phpcms也为了完善 ...

  7. JDK + Tomcat 安装配置

    学习Java 开发的第一步就是配置环境,今天第一次配置,把过程记录下以备后用. 一.下载JDK.Tomcat JDK:http://www.oracle.com/technetwork/java/ja ...

  8. CREATE DATABASE

    CREATE DATABASE IF NOT EXISTS `focusdata` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;USE `fo ...

  9. android studio如何查看数据库文件

    android studio查看数据库文件有两种方式: 1.SQLSCOUT 优点:集成在as中,功能强大. 缺点:收费,破解麻烦. 2.Android Device Monitor 中的File E ...

  10. for_each 用法!

    class MapTest:public CapTest{ private: set <string> MyTestContain; typedef pair <string,int ...