iOS多线程编程——浅谈GCD

GCD对于iOS开发者来说肯定不陌生,他和NSThread,NSOperation一起作为iOS开发中主要的三种多线程实现方法,而GCD是最最底层的,所以对于作为一个iOSer,GCD是必须掌握的。

我通过对于以下两篇文章的阅读,基本上掌握了GCD的基本使用方法。所以首先感谢两位作者。

一、基本概念



对于新手来说,最常见同时最容易搞混的的莫过于GCD中的一些基本概念了。

  1. 并行与并发(Parallelism && Concurrency)

    • 并行:顾名思义就是同时行动,两个任务在两个线程(Thread)上进行处理,彼此互不干涉。而究其根本是因为多核进行处理,从而更快的解决任务。(可以类比于两条水管中同时给游泳池放水)
    • 并发:顾名思义,同时发生。对于有的性能较差的机子,比如说那些只有单核的设备,为了让用户感觉能够同时处理多个任务,他就需要通过不断的切换正在处理的线程,从而实现一种“伪并行”,这样就防止用户因为一个任务进行太久而无法进行下一个任务。(可以类比于我用两个口给游泳池放水,不过因为只有一根水管,而放水的方向有2个方面,所以需要不断的切换,从而实现两边同时达到需要的水位)
    • 区分如下图:
  2. 串行与并发(Serial && Concurrent)
    • 串行:由于所有任务在一个线程被执行的时候只有一个任务会被执行,所以每个任务都要依赖于先来先处理(FIFO)原则,处理所有得到的内容。
    • 并行:当接收到任务的时候,并行状态下,他会开启多个线程,将每个任务都分配到各个线程中,从而使得每个任务都能够在同一时间被执行。这样有效的减少了所消耗的时间。
    • 区分图如下:

  3. 队列:就像前面所说,当你告诉电脑你需要执行哪些事情的时候,电脑就会把你告诉他的一件件事情放到自己的队伍中,很明显,先告诉他的事情放在前面,后告诉他的事情放在后面,即先进先处理(FIFO)原则处理所有事物,就像排队一样。
    • 在GCD中主要的队列有2种,就是前面提到的:

      • 串行队列
      • 并发队列
    • 苹果公司提供给我们的已存在的队列有5种(不包括后期我们自己添加的内容)
      • 主队列(main queue):默认情况下所有事情都是在主队列下进行处理,只要有编码经验的人来说,主队列很明显是(串行队列)。
      • 全局调度队列(Global Dispatch Queues):默认情况下全局队列是系统提供的并发队列的统称,根据队列的优先程度不同分为以下几个(优先度从低到高):
        • background
        • low
        • default
        • high
  4. 同步与异步(synchronization && asynchronous)

    -同步:同步指的是在原来的内容执行完成之后,再执行你下面所需要的任务。

    -异步:异步指的是在和原来的内容执行的同时,在另一个地方同时处理新的任务。

二、基本使用



既然学了,那肯定要使用它。那么我们先模拟一个环境:

这个程序里面只有主队列,然后你想从网上下载一个图片,放到你自己的手机里面,然后用户就可以对手机里面的这张图片进行各种各样的处理。

但是问题来了:我们都知道下载需要时间,虽然现在已经是4G时代,下载速度很快,但是如果这个图片很大,那么他就需要很长的时间来进行等待。而在这段时间内,用户什么事情都不能干,只能默默的干等着。

这很明显是一个很差的用户体验

那么既然我们已经知道问题所在,就需要想办法解决它:

  1. 我们需要在用户等待的时候添加一个动画效果,告诉他我们正在下载(这个和今天主题无关,我们就不继续深入了解)
  2. 将下载任务放到后台(如果仍旧放在前台的话,就算有动画,动画效果也不会动)

那么这个时候就需要异步队列的存在了



就像前面的基本概念中所提到的,执行分为同步执行和异步执行,任务队列分为串行队列和并发队列。所以他们彼此一一结合,总共有4种情况存在,主要的彼此结合的状态图如下所示:

单队列

串行队列,同步执行

代码如下:

func serialDispatchQueueWithSync() {
    let queue = dispatch_queue_create("serialDispatchQueue", DISPATCH_QUEUE_SERIAL)

    print("0")

    dispatch_sync(queue) {
        print("1")
    }
    dispatch_sync(queue) {
        print("2")
    }
    dispatch_sync(queue) {
        print("3")
    }
    dispatch_sync(queue) {
        print("4")
    }
    dispatch_sync(queue) {
        print("5")
    }
    print("6")
}

运行结果如下:

0
1
2
3
4
5
6

串行队列,异步执行

代码如下:

func serialDispatchQueueWithASync() {
    let queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL)

    print("0")

    dispatch_async(queue) {
        print("1")
    }
    dispatch_async(queue) {
        print("2")
    }
    dispatch_async(queue) {
        print("3")
    }
    dispatch_async(queue) {
        print("4")
    }
    dispatch_async(queue) {
        print("5")
    }
    dispatch_async(queue) {
        print("6")
    }
    print("7")
}

运行结果如下:

0
7
1
2
3
4
5
6

解释:因为queue是异步执行的,即调用dispatch_async函数,所以输出1 2 3 4 5 6的时候是在另一个线程中的,所以和主线程中的输出1 7 无关。

并发队列,同步执行

代码如下:

func concurrentDispatchsQueueWithSync() {
    let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)

    print("0")

    dispatch_sync(queue) {
        print("1")
        print("12")
        print("13")
    }
    dispatch_sync(queue) {
        print("2")
        print("22")
        print("23")
    }
    dispatch_sync(queue) {
        print("3")
        print("32")
        print("33")
    }
    dispatch_sync(queue) {
        print("4")
        print("42")
        print("43")
    }
    print("5")
}

运行结果如下:

0
1
12
13
2
22
23
3
32
33
4
42
43
5

并发队列,异步执行

代码如下:

func concurrentDispatchsQueueWithASync() {
    let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)

    print("0")

    dispatch_async(queue) {
        print("1")
        print("12")
        print("13")
    }
    dispatch_async(queue) {
        print("2")
        print("22")
        print("23")
    }
    dispatch_async(queue) {
        print("3")
        print("32")
        print("33")
    }
    dispatch_async(queue) {
        print("4")
        print("42")
        print("43")
    }
    print("5")
}

运行结果如下:

0
7
1
2
12
3
22
13
4
32
23
5
42
33
52
43
53

其他常见用法:

挂起与恢复
  • 线程挂起:dispatch_suspnd()
  • 线程恢复:dispatch_resume()
信号量

由于有的时候(比如说在创建添加数组中的对象的时候),因为不同线程操纵的是同一个对象,所以很容易发生报错,这个时候需要通过信号量来对对应的内容进行控制,当信号量为0的时候,进入等待状态,不能执行下面的内容;当信号量为1的时候,可以执行,同时信号量减一,等到执行完毕,信号量加一。

- 等待执行dispatch_semaphore_wait()

- 信号量加一 dispatch_semahore_signal

只执行一次

有的时候,有的东西的创建只能被创建一次(即单例),这个时候就需要用到dispatch_once()

代码如下:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        println("This is printed only on the first call to test()")
    }
    println("This is printed for each call to test()")
}

关于读写问题的解决方法(买票问题)

由于日常编码的过程中经常会遇到,当你在调用这个这个变量的时候。另一个线程也在调用这个变量(只在并发过程中),如果两个线程都是在读取数据,那并没有什么问题,但是如果其中一个在写入,或者两个都在写入,那么就会出现很大问题,所以苹果官方在GCD中也给我们准备了一个方法,让我们解决这个问题,那就是dispatch_barrier_async,这个方法让对的内容在并发过程中加入,从而方便组织内容的修改,从而使得对应的内容只能在当前线程中被进行修改。

常见问题:

死锁:
let queue = dispatch_get_main_queue()

dispatch_async(queue) {
    dispatch_sync(queue, {
        print("1")
    })
}

产生原理:因为dispatch_sync()会等到本身结束之后才会在主线程继续执行接下去的代码,但是dispatch_sync()这个方法调用的就是主线程,所以午饭等到主线程结束,所以就无法返回,就会卡在这里。

解释:由于是并发队列,所以他会创建多个线程,从而保证每个线程的任务都能够尽快完成,所以顺序有一定的出入。

最后通过两张动态图来最后总结单队列:

dispatch_sync
- (void)viewDidLoad
{
  [super viewDidLoad];

  dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

    NSLog(@"First Log");

  });

  NSLog(@"Second Log");
}

  1. 主队列按照预定的顺序下来
  2. viewDidLoad在主线程进行执行。
  3. 直到执行到dispatch_sync
  4. 调用dispatch_sync代码,将block添加到全局队列中,主队列挂起。
  5. 全局队列先完成之前存放在全局队列中的内容。
  6. 完成之前的任务后,执行dispatch_sync的block中的内容。
  7. 完成block中的任务,主队列上的任务得以恢复
  8. 主队列继续执行其他任务。
dispatch_async
- (void)viewDidLoad
{
  [super viewDidLoad];

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

    NSLog(@"First Log");

  });

  NSLog(@"Second Log");
}

![dispatch_sync](https://camo.githubusercontent.com/2c7cbaf76001a56622e14cf48a8d914d4b5c9df4/687474703a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f64697370617463685f6173796e635f696e5f616374696f6e2e676966)
  1. 主队列一路按顺序执行任务——接着是一个实例化UIViewController 的任务,其中包含了viewDidLoad
  2. viewDidLoad在主线程执行。
  3. 主线程目前在viewDidLoad内,正要到达dispatch_async
  4. dispatch_async Block被添加到一个全局队列中,将在稍后执行。
  5. viewDidLoad在添加dispatch_async到全局队列后继续进行,主线程把注意力转向剩下的任务。同时,全局队列并发地处理它未完成地任务。记住Block在全局队列中将按照(FIFO)顺序出列,但可以并发执行。
  6. 添加到dispatch_async的代码块开始执行。
  7. dispatch_async Block完成,两个NSLog语句将它们的输出放在控制台上。

调度组

既然能够处理一个个的任务,那么我们就继续模拟一个环境,当我们需要在网上下载内容的时候(这些内容需要彼此联系在一起才能正常使用),这个时候,上面的单队列就不够了(或者说如果使用单队列产生的效果不是时间太长,就是文件的完整性不够好)

这个时候我们就需要引入多队列,当多个内容都处理完成之后,让系统告诉我们,我们已经完成了以上的下载。可以继续做下一步事情了。

任务开始

在GCD中,我们可以通过dispatch_group_enter来通知当前任务的开始,而与之相对应的,我们必须在任务完成后,手动通知调度组任务结束(dispatch_group_leave)这样才能让调度组知道我们这个任务已经结束。

代码如下(由于demo中有一个Photo类,所以此处贴上OC代码):

dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) {
    if (_error) {
        error = _error;
    }
dispatch_group_leave(downloadGroup); // 4
}];

[[PhotoManager sharedManager] addPhoto:photo];

任务提醒

当我们所有的任务都手动通知后,那么就需要条用提醒来告诉他(我已经完成了所有内容,接下去需要试试哪里能够执行了),而提醒方式在GCD中有两种:

  • dispatch_group_wait() + dispatch_async()
  • dispatch_group_notify

其他常见用法

dispatch_apply()

有的时候需要调用for循环来反复执行,但是当需要执行的代码量偏大的时候,for的效率比较低,这个时候需要用dispatch_apply()来执行,这样节省效率,不过当需要执行的代码量比较小的时候,dispatch_apply()的效率就比较差了。

这个方法的效果和dispatch_sync一样,所以要注意死锁(后面会提到)

时间: 2024-08-08 09:34:27

iOS多线程编程——浅谈GCD的相关文章

iOS多线程编程(四)------ GCD(Grand Central Dispatch)

一.简介 是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法,用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch_queue).GCD会负责创建线程和调度你的任务,系统会直接提供线程管理. 二.任务和队列 GCD中有两个核心概念 (1)任务:执行什么操作 (2)队列:用来存放任务 GCD的使用就

iOS多线程编程

1. 进程,线程, 任务 进程:一个程序在运行时,系统会为其分配一个进程,用以管理他的一些资源. 线程:进程内所包含的一个或多个执行单元称为线程,线程一般情况下不持有资源,但可以使用其所在进程的资源. 任务:进程或线程中要做的事情. 在引入线程的操作系统中,通常把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位. 线程比进程更小,对其调度的开销小,能够提高系统内多个任务的并发执行程度. 一个程序至少有一个进程,一个进程至少有一个线程.一个程序就是一个进程,而一个程序中的多个任

线程同步-iOS多线程编程指南(四)-08-多线程

首页 编程指南 Grand Central Dispatch 基本概念 多核心的性能 Dispatch Sources 完结 外传:dispatch_once(上) Block非官方编程指南 基础 内存管理 揭开神秘面纱(上) 揭开神秘面纱(下) iOS多线程编程指南 关于多线程编程 线程管理 Run Loop 线程同步 附录 Core Animation编程指南 Core Animation简介 基本概念 渲染架构 几何变换 查看目录 中文手册/API ASIHTTPRequest Openg

iOS多线程开发之深入GCD

iOS多线程开发之深入GCD 一.前言 在以前的一些系列博客中,对iOS中线程的管理做了总结,其中涵盖了GCD的相关基础知识:http://my.oschina.net/u/2340880/blog/417746.那里面将GCD的线程管理能力,列队组能力,通过信号和消息控制程序流程的能力都有介绍,这里,我们继续深入GCD的功能,通过GCD来处理一些逻辑更加复杂的代码功能. 二.延时追加任务 当我们在程序中处理延时任务的时候,我们一般会通过两种方式,一种是通过定时器进行延时执行,另外一种是通过如下

iOS多线程编程Part 1/3 - NSThread & Run Loop

iOS多线程编程Part 1/3 - NSThread & Run Loop 02 JUNE 2013 前言 多线程的价值无需赘述,对于App性能和用户体验都有着至关重要的意义,在iOS开发中,Apple提供了不同的技术支持多线程编程,除了跨平台的pthread之外,还提供了NSThread.NSOperationQueue.GCD等多线程技术,从本篇Blog开始介绍这几种多线程技术的细节. 对于pthread这种跨平台的多线程技术,这本Programming with POSIX Thread

iOS多线程开发小demo7 GCD队列组

// DYFViewController.m // 623-08-队列组 // // Created by dyf on 14-6-23. // Copyright (c) 2014年 ___FULLUSERNAME___. All rights reserved. // #import "DYFViewController.h" @interface DYFViewController () @property (weak, nonatomic) IBOutlet UIImageVi

iOS多线程开发小demo6 GCD

// DYFViewController.m // 623-07-GCD // // Created by dyf on 14-6-23. // Copyright (c) 2014年 ___FULLUSERNAME___. All rights reserved. // #import "DYFViewController.h" @interface DYFViewController () @property (weak, nonatomic) IBOutlet UIImageVi

【转】iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

转自容芳志的博客 简介 iOS有三种多线程编程的技术,分别是: (一)NSThread (二)Cocoa NSOperation (三)GCD(全称:Grand Central Dispatch) 这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的. 三种方式的优缺点介绍: 1)NSThread: 优点:NSThread 比其他两个轻量级 缺点:需要自己管理线程的生命周期,线程同步.线程同步对数据的加锁会有一定的系统开销 NSThread实现的技术

iOS多线程编程Part 3/3 - GCD

前两部分介绍了NSThread.NSRunLoop和NSOperation,本文聊聊2011年WWDC时推出的神器GCD.GCD: Grand Central Dispatch,是一组用于实现并发编程的C接口.GCD是基于Objective-C的Block特性开发的,基本业务逻辑和NSOperation很像,都是将工作添加到一个队列,由系统来负责线程的生成和调度.由于是直接使用Block,因此比NSOperation子类使用起来更方便,大大降低了多线程开发的门槛.另外,GCD是开源的喔:libd