1. 1. 概述
  2. 2. 多线程
    1. 2.1. 介绍
      1. 2.1.1. 进程
      2. 2.1.2. 线程
      3. 2.1.3. 线程的串行
      4. 2.1.4. 多线程的并行
      5. 2.1.5. 多任务系统调度
        1. 2.1.5.1. windows多任务
    2. 2.2. 多线程的优缺点
      1. 2.2.1. 多线程的优点
      2. 2.2.2. 多线程的缺点
    3. 2.3. ios中的多线程
      1. 2.3.1. NSThread
      2. 2.3.2. NSOperation
      3. 2.3.3. GCD
  3. 3. GCD
    1. 3.1. 介绍
      1. 3.1.1. GCD是什么
      2. 3.1.2. 设计
    2. 3.2. 术语
      1. 3.2.1. dispath queue**(调度队列)**
        1. 3.2.1.1. Serial(串行队列)
        2. 3.2.1.2. Concurrent(并行队列)
        3. 3.2.1.3. Global dispatch queue(全局队列)
        4. 3.2.1.4. Main dispatch queue(主队列)
      2. 3.2.2. Queue Types (队列类型)
      3. 3.2.3. 操作(任务)
        1. 3.2.3.1. 同步
        2. 3.2.3.2. 异步
        3. 3.2.3.3. 函数指针调度任务
      4. 3.2.4. Deadlock 死锁
      5. 3.2.5. Concurrency vs Parallelism 并发与并行
    3. 3.3. 创建和管理dispatch queue(队列)
      1. 3.3.1. 创建串行Dispatch Queue
      2. 3.3.2. 创建并行Dispatch Queue
      3. 3.3.3. dispatch_sync(同步调用)
    4. 3.4. 不同操作及队列线程研究测试
      1. 3.4.1. 主线程
      2. 3.4.2. 串行队列同步调用
        1. 3.4.2.0.1. 示例程序(主线程):
        2. 3.4.2.0.2. 示例程序(子线程):
      3. 3.4.2.1. performSelector
    5. 3.4.3. 调度(队列)组
      1. 3.4.3.1. 手动管理运行状态(或计数)
      2. 3.4.3.2. 方法
    6. 3.4.4. NSInvocationOperation
      1. 3.4.4.1. start
      2. 3.4.4.2. 添加到队列
      3. 3.4.4.3. 添加多个操作
      4. 3.4.4.4. 结论
    7. 3.4.5. NSBlockOperation
      1. 3.4.5.1. 将任务添加到操作
      2. 3.4.5.2. 向队列中添加不同的操作
    8. 3.4.6. NSOperation依赖关系
      1. 3.4.6.1. 示例代码
      2. 3.4.6.2. 输出结果
    9. 3.4.7. 其他方法
      1. 3.4.7.1. Initialization
      2. 3.4.7.2. Executing the Operation
      3. 3.4.7.3. Waiting for Completion
    10. 3.4.8. 队列中Operations的执行顺序
    11. 3.4.9. 最大并发操作数量
    12. 3.4.10. 全局队列
    13. 3.4.11. 暂停和继续
    14. 3.4.12. 挂起和回复队列
  4. 3.5. 取消Operations
    1. 3.5.1. 示例代码
  5. 3.6. 等待Options完成
  6. 3.7. 开发建议
  • 4. NSOperation.VS.GCD
  • 5. RunLoop
    1. 5.1. 什么是RunLoop
    2. 5.2. 事件源
      1. 5.2.1. NSPort 基于端口的源
      2. 5.2.2. 自定义输入源
      3. 5.2.3. performSelector:OnThread
      4. 5.2.4. 定时源
    3. 5.3. RunLoop观察者
    4. 5.4. Run Loop Modes
  • ios多线程之NSThread,GCD,NSOperation以及线程同步

    概述

    大家都知道,我们在很多app的使用中很关注的一个体验就是app的流畅性以及用户等待时间.而这两点其实最根本就的就是让程序已最少的时间完成运算.无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的,一个复杂的多步操作只能一步步按顺序逐个执行.所以我们就会引入这个线程技术.然后这里初学者很容易有一个误区是:(“多线程技术会提高单个算法本身的执行效率.”这是非常严重的一个错误.多线程技术并不会和程序本身的执行效率有关系,只是将一些耗时的任务分配到其他线程执行,进行一个”并行”运算).对于单核处理器,可以将多个步骤放到不同的线程,这样一来用户完成UI操作后其他后续任务在其他线程中,当CPU空闲时会继续执行,而此时对于用户而言可以继续进行其他操作;对于多核处理器,如果用户在UI线程中完成某个操作之后,其他后续操作在别的线程中继续执行,用户同样可以继续进行其他UI操作,与此同时前一个操作的后续任务可以分散到多个空闲CPU中继续执行(当然具体调度顺序要根据程序设计而定),既解决了线程阻塞又提高了运行效率.苹果从iPad2 开始使用双核A5处理器(iPhone中从iPhone 4S开始使用),A7中还加入了协处理器,如何充分发挥这些处理器的性能确实值得思考.

    多线程

    介绍

    当用户播放音频、下载资源、进行图像处理时往往希望做这些事情的时候其他操作不会被中断或者希望这些操作过程中更加顺畅。在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验。早在单核处理器时期就有多线程,这个时候多线程更多的用于解决线程阻塞造成的用户等待(通常是操作完UI后用户不再干涉,其他线程在等待队列中,CPU一旦空闲就继续执行,不影响用户其他UI操作),其处理能力并没有明显的变化。如今无论是移动操作系统还是PC、服务器都是多核处理器,于是“并行运算”就更多的被提及。一件事情我们可以分成多个步骤,在没有顺序要求的情况下使用多线程既能解决线程阻塞又能充分利用多核处理器运行能力。

    下图反映了一个包含8个操作的任务在一个有两核心的CPU中创建四个线程运行的情况。假设每个核心有两个线程,那么每个CPU中两个线程会交替执行,两个CPU之间的操作会并行运算。单就一个CPU而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高,多CPU的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。当然,不管是多核还是单核开发人员不用过多的担心,因为任务具体分配给几个CPU运算是由系统调度的,开发人员不用过多关心系统有几个CPU。开发人员需要关心的是线程之间的依赖关系,因为有些操作必须在某个操作完成完才能执行,如果不能保证这个顺序势必会造成程序问题。

    202333357067475

    进程

    进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

    QQ20150731-1@2x

    (通过“活动监视器”可以查看Mac系统中所开启的进程)

    如图所示打开QQ、Xcode,Finder等系统就会分别启动n个进程..

    线程

    当一个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,这条线程也称之为”主线程”).线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行.

    QQ20150731-2@2x

    如图所示,谷歌浏览器开辟了35条线程之多,比如谷歌浏览器的查看网页,下载,播放Flash等所有的操作都需要在线程中执行.

    并且线程是无法手动杀死的,只能暂停(或者叫休眠线程).

    使用线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现”同时运行”.

    线程的串行

    1个线程中任务的执行是串行的(要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务)

    也就是说,在同一时间内,1个线程只能执行1个任务.此时是线程的串行,请勿于队列的串行和并发混淆.

    QQ20150731-3@2x

    如图所示,现在有3个下载任务,但是只有单一的线程.首先讲下载任务1丢进去,当下载任务1执行完毕后在丢入下载任务2.

    多线程的并行

    1个进程中可以开启多条线程,每条线程可以”并行”(”同时”)执行不同的任务

    多线程技术可以提高程序的执行效率,使程序更加流畅,用户体验更好.(再次降调一次:多线程技术并不会提高单个算法本身的执行效率)

    比如同时开启3条线程分别下载3个文件(分别是下载任务1、下载任务2、下载任务3)

    QQ20150731-4@2x

    如图所示,我们在这个进程中开辟3条线程,并且把下载任务1,2,3分别丢入线程中.这时3个下载任务就可以通过多任务系统调度后”并行”(“同时”)下载了.

    多任务系统调度

    • windows多任务

    它是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务。Windows就是一个支持多任务的操作系统,比起DOS的单任务系统,确实方便了许多。Windows多任务处理采用的是被称为虚拟机(Virtual Machine)的技术。虚拟机实际上指的是由Windows在内存中创建的逻辑微机,由它来运行应用程序。当Windows接受到由鼠标器、键盘、定时器信号或某些I/O操作产生的”事件”后,为该任务分配CPU时间。每个任务使用由Windows分配的短暂的时间片(Timeslice)轮流使用CPU,由于CPU对每个时间片的处理速度非常快,在用户看来好像这些任务在同时执行
    ios的多程序和windows的多任务类似.具体关于多可以资料可以查看”各种系统以及构架多任务的讨论(x86、arm、unix、windows其他)“.

    大家可能注意到了,我之前的说的所有并且及同时都有引号,因为:每个应用程序由操作系统分配的非常短暂的时间片(Timeslice)轮流使用CPU,由于CPU对每个时间片的处理速度非常快.因此,用户看来好像这些任务在同时执行的.所以并行其实是指两个或多个任务在同一时间间隔内发生.(但是在任意一个时刻点上,CPU只会处理一个任务.)

    多线程的优缺点

    多线程的优点

    1. 能适当提高程序的执行效率
    2. 当硬件处理器的数量增加,程序会运行更快,而程序无需做任何调整
    3. 将耗时的任务分配到其他线程执行,由主线程负责统一更新界面会使应用程序更加流畅,用户体验更好
    4. 充分发挥多核处理器优势,将不同线程任务分配给不同的处理器,真正进入“并行运算”状态.提高资源利用率(CPU,内存利用率)

    多线程的缺点

    1. 开启线程需要占用一定的内存空间(默认情况下:主线程占用1M,子线程占用512KB.注意:实际测试中,主线程也是512k),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    2. 线程越多,CPU在调度线程上的开销就越大
    3. 比如线程之间的通信,多线程的数据共享

    ios中的多线程

    在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。

    主线程作用:显示\刷新UI界面以及处理UI事件(比如点击事件、滚动事件、拖拽事件等)

    由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面(新版iOS中,使用其他线程更新UI可能也能成功,但是不推荐)。

    iOS中多线程使用并不复杂,关键是如何控制好各个线程的执行顺序、处理好资源竞争问题。常用的多线程开发有三种方式:

    NSThread

    使用NSThread对象建立一个线程非常方便

    使用NSThread管理多个线程非常困难.(不推荐使用)

    NSOperation

    使用GCD实现的一套Objective-C的API,简单说就是封装好的GCD.

    面向对象的线程技术

    提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系等

    使用[NSThread currentThread]来跟踪任务所在线程,适用于这三种技术,并且非常好用!

    GCD

    是基于C语言的底层API

    用Block定义任务,使用起来非常灵活便捷

    提供了更多的控制能力以及操作队列中所不能使用的底层函数

    充分利用了多核处理器的运算性能

    自动管理线程的生命周期(创建线程,调度任务,销毁线程)

    三种方式是随着iOS的发展逐渐引入的,所以相比而言后者比前者更加简单易用,并且GCD也是目前苹果官方比较推荐的方式(它充分利用了多核处理器的运算性能)。

    GCD

    GCD的基本思想是就将操作(任务)放在队列(调度)中去执行.

    介绍

    GCD是什么

    Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

    设计

    GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务

    一个任务可以是一个函数(function)或者是一个block

    GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节



    队列负责调度任务执行所在的线程以及具体的执行时间


    GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行

    术语

    如果要理解 GCD之前 ,首先需要了解线程和并发相关的几个概念。这两者都可能模糊和微妙,所以在开始 GCD 之前先简要地回顾一下它们。

    dispath queue**(调度队列)**

    队列就是专门用来存放任务(操作)的.



    GCD的一个重要概念是队列.它的核心理念:将长期运行的任务拆分成多个工作单元(代码块),并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程.

    系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue.也可以创建自己的dispath queue,而且可以创建任意多个.GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动.

    dispatch queue按先进先出的顺序,串行或并发地执行任务

    serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务

    concurrent dispatch queue则尽可能多地启动任务并发执行


    >
    所有的调度队列(dispatch queues)自身都是线程安全的,你能从多个线程并行的访问它们。 GCD 的优点是显而易见的,即当你了解了调度队列如何为你自己代码的不同部分提供线程安全。关于这一点的关键是选择正确类型的调度队列和正确的调度函数来提交你的工作。
    Serial(串行队列)

    又称为private dispatch queues,同时只执行一个任务.Serial queue通常用于同步访问特定的资源或数据.只有一个线程,加入到队列中的操作按添加顺序依次执行.

    4673_140428093515_1

    这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。


    由于在串行队列中不会有两个任务并发运行,因此不会出现同时访问临界区的风险;相对于这些任务来说,这就从竞态条件下保护了临界区。所以如果访问临界区的唯一方式是通过提交到调度队列的任务,那么你就不需要担心临界区的安全问题了。
    Concurrent(并行队列)

    有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理.(和全局队列类似,只是创建后可以调试时观察”const char *label“来确定线程.)

    Global dispatch queue(全局队列)

    是所有应用程序都能够使用的Concurrent.一般不用刻意的去创建一个Concurrent 的Queue.只要获得系统的这个即可.

    Main dispatch queue(主队列)

    将任务放在主线程中去执行,可以将一些更新UI的任务追加到该Queue中,这个和NSObject类提供的performSelectorOnMainThread方法执行的效果一样.(其实在NSOperation中也有一个主队列)

    Queue Types (队列类型)

    系统提供了一个叫做主队列(main queue) 的特殊队列.和其它串行队列一样,这个队列中的任务一次只能执行一个.它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程.这个队列就是用于发生消息给 UIView 或发送通知等.



    系统同时提供给你好几个并发队列(全局队列)
    /!
      @function dispatch_get_global_queue
    @abstract
    Returns a well-known global concurrent queue of a given quality of service class.
    @discussion
    The well-known global concurrent queues may not be modified. Calls to dispatch_suspend(), dispatch_resume(), dispatch_set_context(), etc., will
    have no effect when used with queues returned by this function.
    @param identifier A quality of service class defined in qos_class_t or a priority defined in
    dispatch_queue_priority_t.
    It is recommended to use quality of service class values to identify the well-known global concurrent queues:
    - QOS_CLASS_USER_INTERACTIVE - QOS_CLASS_USER_INITIATED
    - QOS_CLASS_DEFAULT - QOS_CLASS_UTILITY
    - QOS_CLASS_BACKGROUND
    The global concurrent queues may still be identified by their priority, which map to the following QOS classes:
    - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
    - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
    @param flags
    Reserved for future use. Passing any value other than zero may result in a NULL return value.
    @result
    Returns the requested global queue or NULL if the requested global queue does not exist.
    /
    OSX_AVAILABLE_STARTING(MAC_10_6,__IPHONE_4_0)
    DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW
    dispatch_queue_t
    dispatch_get_global_queue(long identifier, unsigned long flags);


    ##### 全局优先级

    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高

    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)

    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低

    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

    它们叫做 全局调度队列(Global Dispatch Queues).目前的四个全局队列有着不同的优先级:background、low、default 以及 high.要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务.


    > 不建议使用不同优先级的队列,因为如果设计不当,可能会出现优先级反转,即低优先级的操作阻塞高优先级的操作.
    >
    >
    > 一般都默认丢入default.

    ##### 全局服务质量(QOS)-ios8推出

    全局服务质量提供了5中不同的优先级以及一个没有制定QOS分别为:

    - QOS_CLASS_USER_INTERACTIVE // 用户交互(会要求CPU尽可能的调度此任务,所以耗时操作不应该使用这个服务质量)

    - QOS_CLASS_USER_INITIATED // 用户发起(如果用户希望任务尽快执行完毕返回结果,并且耗时操作也不应该使用次服务质量)

    - QOS_CLASS_DEFAULT // 默认

    - QOS_CLASS_UTILITY //使用 (耗时操作可以使用此服务质量)

    - QOS_CLASS_BACKGROUND // 后台(指定为最节能的方式运行”性能优先”)

    * - QOS_CLASS_UNSOECIFIED // 没有制定QOS

    > 不建议使用不同优先级的队列,因为如果设计不当,可能会出现优先级反转,即低优先级的操作阻塞高优先级的操作.
    >
    >
    > 一般都默认丢入default.

    ##### 获取全局队列ios7,8适配

    如上所示一般在获取全局队列时,服务质量以及优先级我们都默认传入0来适配.
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);

     
    31313131_3123131

    最后,你也可以创建自己的串行队列或并发队列。这就是说,可以有五个队列任你发掘;主队列,四个全局调度队列(其中一个为低的后台队列(用于 I/O)),再加上任何你自己创建的队列。(但四个全局调度一般都不研究,直接丢入default).并且队列是可以嵌套的.

    操作(任务)

    同步

    执行完这一句代码,再执行后续的代码

    就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

    你打电话问书店老板有没有 <<BirdMichael的GCD讲解>> 这本书,如果是同步通信机制—书店老板会说:”你稍等,我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果).这其中你电话不可以挂断.(等待返回结果).接收不到返回结果,不继续下面的操作(淘宝买).

    在当前线程中执行任务,不具备开启新线程的能力

    异步

    不必等待这一句代码执行完,就执行下一句代码

    调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

    你打电话问书店老板有没有 <<BirdMichael的GCD讲解>> 这本书,如果是异步通信机制—可能你你正在执行某个操作(通过淘宝查询),然后拿起电话给书店老板打了一个电话.书店老板直接告诉你:”我查一下啊,查好了打电话给你”,然后直接挂了电话(不返回结果).你继续该干哈干哈.等老板然后查好了,他会主动打电话给你,在这里老板通过“回电”这种方式来回调.

    在新的线程中执行任务,具备开启新线程的能力

    函数指针调度任务

    也就是dispatch_sync_f函数和dispatch_async_f函数.函数指针的传递类属于pthread.

    函数指针调度几乎不用!!只为面试了解即可.

    Deadlock 死锁

    两个(有时更多)东西在大多数情况下,是线程.所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作.第一个不能完成是因为它在等待第二个的完成.但第二个也不能完成,因为它在等待第一个的完成.


    // 全局队列,都在主线程上执行,不会死锁
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 并行队列,都在主线程上执行,不会死锁
    dispatch_queue_t q = dispatch_queue_create(“com.birdmichael.concurrent”, DISPATCH_QUEUE_CONCURRENT);
    // 串行队列,会死锁,但是会执行嵌套同步操作之前的代码
    dispatch_queue_t q = dispatch_queue_create(“com.birdmichael.serial”, DISPATCH_QUEUE_SERIAL);
    // 直接死锁
    dispatch_queue_t q = dispatch_get_main_queue();

    dispatch_sync(q, ^{
    NSLog(@”同步任务 %@”, [NSThread currentThread]);
    dispatch_sync(q, ^{
    NSLog(@”同步任务 %@”, [NSThread currentThread]);
    });
    });




    #### Context Switch 上下文切换

    一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程.这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。

    Concurrency vs Parallelism 并发与并行

    并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别.

    并发代码的不同部分可以“同步”执行.然而,该怎样发生或是否发生都取决于系统.多核设备通过并行来同时执行多个线.然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程.这通常发生地足够快以致给我们并发执行地错觉.如下图所示:


    4673_140428093433_1

    虽然你可以编写代码在 GCD 下并发执行,但 GCD 会决定有多少并行的需求.并行要求并发,但并发并不能保证并行.

    更深入的观点是并发实际上是关于构造.当你在脑海中用 GCD 编写代码,你组织你的代码来暴露能同时运行的多个工作片段,以及不能同时运行的那些.如果你想深入此主题.看看 this excellent talk by Rob Pike .

    >
    简单的说,并发和并行其实一个东西.在多核中,使用并行.每个核处理不同任务.而在单核中,为了打消同样的效果,就通过上面讲到的时间片的方式通过伪多任务实现.

    创建和管理dispatch queue(队列)

    创建串行Dispatch Queue

    应用的任务需要按特定顺序执行时,就需要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁

    你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue

    利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性

    dispatch_queue_t queue = dispatch_queue_create("com.birdmichael.serial, NULL);
    或者:
    dispatch_queue_t queue = dispatch_queue_create("com.birdmichael.serial, DISPATCH_QUEUE_SERIAL);
    /*!
     * @const DISPATCH_QUEUE_SERIAL
     * @discussion A dispatch queue that invokes blocks serially in FIFO order.
     */
    #define DISPATCH_QUEUE_SERIAL NULL
    
    /*!
     * @const DISPATCH_QUEUE_CONCURRENT
     * @discussion A dispatch queue that may invoke blocks concurrently and supports
     * barrier blocks submitted with the dispatch barrier API.
     */
    #define DISPATCH_QUEUE_CONCURRENT \
            DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
            _dispatch_queue_attr_concurrent)
    __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_3)
    DISPATCH_EXPORT
    struct dispatch_queue_attr_s _dispatch_queue_attr_concurrent;
    

    创建并行Dispatch Queue

    与全局获取到得并行队列区别是多了一个queue名

    dispatch_queue_t q = dispatch_queue_create("com.birdmichael.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    ![QQ20150801-1@2x](http://birdmichael.com/wp-content/uploads/2015/07/QQ20150801-1@2x.png) 如图所示:调试中,我们可以清楚看到当前队列在哪一个线程 当有多个并发队列或者需要对队列调试才手动创建并行Queue,一般直接获取公共全局Queue. #### 获得公共Queue GCD提供了函数让应用访问几个公共dispatch queue: 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识.在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中).在block对象之外调用这个函数会返回应用的默认并发queue. 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue 使用dispatch_get_global_queue来获得共享的并发queue ##### 获得全局并发Dispatch Queue 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:**可用核数量**、其它**进程正在执行的工作数量**、**其它串行dispatch queue中优先任务的数量**等. 系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue:
    // 获取默认优先级的全局并发dispatch queue
        dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue,默认使用DISPATCH_QUEUE_PRIORITY_DEFAULT;第二个参数目前未使用到,为系统保留项,默认0即可 虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue.(在ARC环境中都不需要管理计数器)因为这些queue对应用是全局的,retain和release调用会被忽略.你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue就行了. ##### 获得(主队列)串行Dispatch Queue
    // 获取主队列串行dispatch queue
        dispatch_queue_t  queue = dispatch_get_main_queue();
    主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行. #### Dispatch Queue的内存管理 **在ARC环境中,不需要管理dispatch queue的内存问题,交由系统管理.在非ARC中,值需要release创建的队列,get的不用release.** Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型.当你创建一个串行dispatch queue时,初始引用计数为 1,你可以使用dispatch_retain和dispatch_release函数来增加和减少引用计数.当引用计数到达 0 时,系统会异步地销毁这个queue 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时能够保留在内存中.和OC对象一样,通用的规则是如果使用一个传递过来的queue,你应该在使用前retain,使用完之后release 你不需要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue 即使你实现的是自动垃圾收集的应用,也需要retain和release创建的dispatch queue和其它dispatch对象.GCD 不支持垃圾收集模型来回收内存 ### **添加任务(操作)到queue(队列)** 要执行一个任务,首先你需要把这个任务由block封装起来.然后把它你需要将它添加到一个适当的dispatch queue.你可以单个或按组来添加,也可以同步或异步地执行单一任务. #### **dispatch_async(异步调用)**
    为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面.
    // 将一个异步调用放入队列中
        dispatch_async(<#dispatch_queue_t queue#>, ^{
            <#code#>
        })

    dispatch_sync(同步调用)

    少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误.比如阻塞并行队列的执行,要求某一操作执行后再进行后续操作,如用户登录.

    // 全局队列,都在主线程上执行,不会死锁
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 并行队列,都在主线程上执行,不会死锁
    dispatch_queue_t q = dispatch_queue_create("com.birdmichael.concurrent", DISPATCH_QUEUE_CONCURRENT);
    // 串行队列,会死锁,但是会执行嵌套同步操作之前的代码
    dispatch_queue_t q = dispatch_queue_create("com.birdmichael.serial", DISPATCH_QUEUE_SERIAL);
    // 直接死锁
    dispatch_queue_t q = dispatch_get_main_queue();
    
    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@", [NSThread currentThread]);
        dispatch_sync(q, ^{
            NSLog(@"同步任务 %@", [NSThread currentThread]);
        });
    });
    

    不同操作及队列线程研究测试

    主线程


    //
    // ViewController.m
    // GCD
    //
    // Created by 李 阳 on 15/7/31.
    // Copyright (c) 2015年 BirdMIchael. All rights reserved.
    //

    #import “ViewController.h”

    @interface ViewController ()

    @end

    @implementation ViewController

    - (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0;i < 10 ;++i)
    {
    NSLog(@”主进程(UI进程)%@ ->%d”,[NSThread currentThread],i);
    }
    }
    @end

    输出结果:
    2015-08-01 16:47:53.018 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->0
    2015-08-01 16:47:53.018 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->1
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->2
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->3
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->4
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->5
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->6
    2015-08-01 16:47:53.019 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->7
    2015-08-01 16:47:53.032 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->8
    2015-08-01 16:47:53.034 GCD[98013:323175] 主进程(UI进程)<NSThread: 0x7f8a52d289a0>{number = 1, name = main} ->9

    结论:都在线程1(主线程)运行,不会开启新线程.

    #### 串行队列异步调用

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self gcdtest];
    }
    // 串行 异步
    - (void)gcdtest
    {
    dispatch_queue_t q = dispatch_queue_create(“com.birdmichael”, DISPATCH_QUEUE_SERIAL);
    for (int i = 0;i < 10 ;++i){
    dispatch_async(q, ^{
    NSLog(@”串行队列,异步调用%@ ->%d”,[NSThread currentThread],i);
    });
    }
    }

    输出:
    2015-08-01 16:57:01.707 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->0
    2015-08-01 16:57:01.728 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->1
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->2
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->3
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->4
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->5
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->6
    2015-08-01 16:57:01.729 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->7
    2015-08-01 16:57:01.734 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->8
    2015-08-01 16:57:01.734 GCD[99504:330237] 串行队列,异步调用<NSThread: 0x7fa3094438f0>{number = 2, name = (null)} ->9

    结论:都在线程2运行,开启新线程,有序.




    #### 并行队列异步调用

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self gcdtest2];
    }
    // 并行 异步
    - (void)gcdtest2
    {
    dispatch_queue_t q = dispatch_queue_create(“com.birdmichael2”, DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0;i < 20 ;++i){
    dispatch_async(q, ^{
    NSLog(@”并行队列,异步调用%@ ->%d”,[NSThread currentThread],i);
    });
    }
    }

    输出:
    2015-08-01 17:16:33.626 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->0
    2015-08-01 17:16:33.626 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->1
    2015-08-01 17:16:33.630 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->2
    2015-08-01 17:16:33.632 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->3
    2015-08-01 17:16:33.643 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->5
    2015-08-01 17:16:33.643 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->6
    2015-08-01 17:16:33.643 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->7
    2015-08-01 17:16:33.644 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->8
    2015-08-01 17:16:33.644 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->9
    2015-08-01 17:16:33.644 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->10
    2015-08-01 17:16:33.645 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->11
    2015-08-01 17:16:33.645 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->12
    2015-08-01 17:16:33.642 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->4
    2015-08-01 17:16:33.646 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->13
    2015-08-01 17:16:33.646 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->14
    2015-08-01 17:16:33.646 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->15
    2015-08-01 17:16:33.647 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->16
    2015-08-01 17:16:33.647 GCD[2997:344440] 并行队列,异步调用<NSThread: 0x7fd758dabe20>{number = 4, name = (null)} ->17
    2015-08-01 17:16:33.648 GCD[2997:344437] 并行队列,异步调用<NSThread: 0x7fd758d9c0d0>{number = 3, name = (null)} ->18
    2015-08-01 17:16:33.649 GCD[2997:344435] 并行队列,异步调用<NSThread: 0x7fd758dab5e0>{number = 2, name = (null)} ->19

    结论:都在线程会多开N个,可以开启新线程,无序.

    串行队列同步调用

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self gcdtest3 ];
    }
    
    // 串行 同步
    - (void)gcdtest3
    {
        dispatch_queue_t q = dispatch_queue_create("com.birdmichael", DISPATCH_QUEUE_SERIAL);
        for (int i = 0;i < 10 ;++i){
            dispatch_sync(q, ^{
                NSLog(@"串行队列,同步调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    }
    输出:
    2015-08-01 17:24:15.133 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->0
    2015-08-01 17:24:15.139 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->1
    2015-08-01 17:24:15.139 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->2
    2015-08-01 17:24:15.139 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->3
    2015-08-01 17:24:15.139 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->4
    2015-08-01 17:24:15.139 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->5
    2015-08-01 17:24:15.140 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->6
    2015-08-01 17:24:15.140 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->7
    2015-08-01 17:24:15.145 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->8
    2015-08-01 17:24:15.145 GCD[4215:349141] 串行队列,同步调用<NSThread: 0x7fb1c1c28200>{number = 1, name = main} ->9
    结论:都再**主线程**运行,**不开启**新线程,**有序**. #### 并行队列同步调用
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self gcdtest4];
    }
    // 并行 同步
    - (void)gcdtest4
    {
        dispatch_queue_t q = dispatch_queue_create("com.birdmichael2", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0;i < 20 ;++i){
            dispatch_sync(q, ^{
                NSLog(@"并行队列,同步调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    }
    输出:
    2015-08-01 17:33:49.704 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->0
    2015-08-01 17:33:49.704 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->1
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->2
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->3
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->4
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->5
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->6
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->7
    2015-08-01 17:33:49.705 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->8
    2015-08-01 17:33:49.706 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->9
    2015-08-01 17:33:49.706 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->10
    2015-08-01 17:33:49.706 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->11
    2015-08-01 17:33:49.706 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->12
    2015-08-01 17:33:49.706 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->13
    2015-08-01 17:33:49.707 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->14
    2015-08-01 17:33:49.707 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->15
    2015-08-01 17:33:49.707 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->16
    2015-08-01 17:33:49.707 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->17
    2015-08-01 17:33:49.707 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->18
    2015-08-01 17:33:49.717 GCD[5743:355585] 并行队列,同步调用<NSThread: 0x7faa8ad11760>{number = 1, name = main} ->19
    结论:都再**主线程**运行,**不开启**新线程,**有序**. #### 并行队列先同步后异步
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self gcdtest5];
    }
    // 并行 同步后异步
    - (void)gcdtest5
    {
        dispatch_queue_t q = dispatch_queue_create("com.birdmichael2", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0;i < 20 ;++i){
            dispatch_sync(q, ^{
                NSLog(@"并行队列,同步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
        for (int i = 0;i < 20 ;++i){
            dispatch_async(q, ^{
                NSLog(@"并行队列,异步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    
    }
    输出:
    2015-08-01 17:46:39.480 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->0
    2015-08-01 17:46:39.480 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->1
    2015-08-01 17:46:39.480 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->2
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->3
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->4
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->5
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->6
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->7
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->8
    2015-08-01 17:46:39.481 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->9
    2015-08-01 17:46:39.482 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->10
    2015-08-01 17:46:39.482 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->11
    2015-08-01 17:46:39.482 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->12
    2015-08-01 17:46:39.482 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->13
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->14
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->15
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->16
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->17
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->18
    2015-08-01 17:46:39.483 GCD[7782:362965] 并行队列,同步 <调用<NSThread: 0x7fd308c28160>{number = 1, name = main} ->19
    2015-08-01 17:46:39.488 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->0
    2015-08-01 17:46:39.489 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->1
    2015-08-01 17:46:39.489 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->2
    2015-08-01 17:46:39.490 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->3
    2015-08-01 17:46:39.491 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->4
    2015-08-01 17:46:39.492 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->5
    2015-08-01 17:46:39.492 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->6
    2015-08-01 17:46:39.493 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->7
    2015-08-01 17:46:39.494 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->8
    2015-08-01 17:46:39.494 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->9
    2015-08-01 17:46:39.494 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->10
    2015-08-01 17:46:39.494 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->11
    2015-08-01 17:46:39.495 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->12
    2015-08-01 17:46:39.495 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->13
    2015-08-01 17:46:39.502 GCD[7782:363061] 并行队列,异步 <调用<NSThread: 0x7fd308d4d510>{number = 4, name = (null)} ->16
    2015-08-01 17:46:39.502 GCD[7782:363061] 并行队列,异步 <调用<NSThread: 0x7fd308d4d510>{number = 4, name = (null)} ->17
    2015-08-01 17:46:39.502 GCD[7782:363061] 并行队列,异步 <调用<NSThread: 0x7fd308d4d510>{number = 4, name = (null)} ->18
    2015-08-01 17:46:39.502 GCD[7782:363061] 并行队列,异步 <调用<NSThread: 0x7fd308d4d510>{number = 4, name = (null)} ->19
    2015-08-01 17:46:39.501 GCD[7782:363054] 并行队列,异步 <调用<NSThread: 0x7fd308d643d0>{number = 2, name = (null)} ->14
    2015-08-01 17:46:39.501 GCD[7782:363057] 并行队列,异步 <调用<NSThread: 0x7fd308c52be0>{number = 3, name = (null)} ->15
    结论:在**主线程**执行**同步**任务,**执行完**以后,在开启**新线程**执行**异步任务**. #### 并行队列先异步后同步
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self gcdtest5];
    }
    // 并行 同步后异步
    - (void)gcdtest5
    {
        dispatch_queue_t q = dispatch_queue_create("com.birdmichael2", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0;i < 20 ;++i){
            dispatch_async(q, ^{
                NSLog(@"并行队列,异步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
        for (int i = 0;i < 20 ;++i){
            dispatch_sync(q, ^{
                NSLog(@"并行队列,同步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    }
    输出:
    2015-08-01 17:50:20.686 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->0
    2015-08-01 17:50:20.686 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->1
    2015-08-01 17:50:20.686 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->0
    2015-08-01 17:50:20.687 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->1
    2015-08-01 17:50:20.687 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->3
    2015-08-01 17:50:20.687 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->4
    2015-08-01 17:50:20.686 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->2
    2015-08-01 17:50:20.687 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->2
    2015-08-01 17:50:20.687 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->6
    2015-08-01 17:50:20.687 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->7
    2015-08-01 17:50:20.687 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->5
    2015-08-01 17:50:20.688 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->3
    2015-08-01 17:50:20.689 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->9
    2015-08-01 17:50:20.689 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->10
    2015-08-01 17:50:20.688 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->8
    2015-08-01 17:50:20.690 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->4
    2015-08-01 17:50:20.690 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->11
    2015-08-01 17:50:20.690 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->12
    2015-08-01 17:50:20.691 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->13
    2015-08-01 17:50:20.691 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->5
    2015-08-01 17:50:20.691 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->14
    2015-08-01 17:50:20.693 GCD[8368:366651] 并行队列,异步 <调用<NSThread: 0x7f94b3d96430>{number = 3, name = (null)} ->15
    2015-08-01 17:50:20.693 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->16
    2015-08-01 17:50:20.694 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->17
    2015-08-01 17:50:20.694 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->6
    2015-08-01 17:50:20.694 GCD[8368:366648] 并行队列,异步 <调用<NSThread: 0x7f94b3d94450>{number = 2, name = (null)} ->18
    2015-08-01 17:50:20.694 GCD[8368:366650] 并行队列,异步 <调用<NSThread: 0x7f94b3d97670>{number = 4, name = (null)} ->19
    2015-08-01 17:50:20.694 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->7
    2015-08-01 17:50:20.694 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->8
    2015-08-01 17:50:20.695 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->9
    2015-08-01 17:50:20.695 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->10
    2015-08-01 17:50:20.695 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->11
    2015-08-01 17:50:20.695 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->12
    2015-08-01 17:50:20.697 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->13
    2015-08-01 17:50:20.697 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->14
    2015-08-01 17:50:20.697 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->15
    2015-08-01 17:50:20.697 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->16
    2015-08-01 17:50:20.697 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->17
    2015-08-01 17:50:20.698 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->18
    2015-08-01 17:50:20.698 GCD[8368:366567] 并行队列,同步 <调用<NSThread: 0x7f94b3c28bd0>{number = 1, name = main} ->19
    结论:异步任务会**穿插**在同步任务中,且同步任务依然在**主线程**按**顺序**执行,异步任务会开启**新线程**在同步任务中穿插,并且**无序** #### 主线程异步调用
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self gcdtest6];
    }
    // 主线程 异步任务
    - (void)gcdtest6
    {
        dispatch_queue_t q = dispatch_get_main_queue();
        for (int i = 0;i < 10 ;++i){
            dispatch_async(q, ^{
                NSLog(@"主线程,异步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    }
    输出:
    2015-08-01 18:03:18.321 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->0
    2015-08-01 18:03:18.322 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->1
    2015-08-01 18:03:18.322 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->2
    2015-08-01 18:03:18.322 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->3
    2015-08-01 18:03:18.322 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->4
    2015-08-01 18:03:18.323 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->5
    2015-08-01 18:03:18.323 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->6
    2015-08-01 18:03:18.323 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->7
    2015-08-01 18:03:18.323 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->8
    2015-08-01 18:03:18.324 GCD[10410:374539] 主线程,异步 <调用<NSThread: 0x7f8d10d28ab0>{number = 1, name = main} ->9
    
    结论:主线程异步会在**主线程**运行,**不开启**新线程,并且**有序** #### 主线程同步调用(死锁)
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"是否被柱塞了?");
        [self gcdtest7];
        NSLog(@"是");
    }
    // 主线程 同步任务
    - (void)gcdtest7
    {
        dispatch_queue_t q = dispatch_get_main_queue();
        for (int i = 0;i < 10 ;++i){
            dispatch_sync(q, ^{
                NSLog(@"主线程,异步 <调用%@ ->%d",[NSThread currentThread],i);
            });
        }
    }
    输出: 程序卡死 ![QQ20150801-2@2x](http://birdmichael.com/wp-content/uploads/2015/07/QQ20150801-2@2x.png)
    2015-08-01 18:07:14.994 GCD[11042:377997] 是否被柱塞了?
    
    结论:除非进程退出,不然结束主线程,所以就无法执行同步调用.后面在代码讲都会卡死在此. #### 总结论 1. 并发队列可以让多个任务并发(同时)执行(自动开启多个想成同时执行任务) 2. 并发队列的功能只有在异步函数下才会由效果 3. 串行队列会让任务一个接一个的执行(执行完毕后在执行下一个任务) 4. 同步(不具备开启新线程能力,都当前区执行)(**非绝对**) 5. 异步(具备开启新线程能力,异就是为不一样的区执行)(**非绝对,比如主队列**) 6. 是否具备开启线程能力只会因为同步还是异步而影响(一般情况) 7. 任务的执行方式只会因为串行和并发而影响(一般情况) 8. 同步一般来说如果不是在嵌套的任务情况下都没什么特别大的意义,因为会在主线程执行(等于直接执行). 9. 异步基本就是多线程的代名词. ### 各队列执行效果对比 ![242038164084931](http://birdmichael.com/wp-content/uploads/2015/07/242038164084931.png) ### 各种队列的选择 #### 串行队列 对执行效率要求不高 对执行顺序要求搞>(必须先xx才可以xx) 性能消耗小
    #### 并发队列 对执行效率要求高 对执行顺序要求不高 对系能消耗大>(涉及关键字"等.","多"等..) #### 全局队列异步任务 如果不想兼顾MRC中的队列释放. ### 其他用法 #### 延时执行 不要使用[NSThread sleepForTimeInterval:(NSTimeInterval)]; 原因:会卡当前程. ##### dispatch_after
    void dispatch_after ( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block );
    when:从现在开始经过多少纳秒 queue:吊物任务的队列 block:代码快(任务,操作) > **但是!一般使用xcode默认的代码块.如下展示** ![QQ20150803-1@2x](http://birdmichael.com/wp-content/uploads/2015/07/QQ20150803-1@2x.png)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        <#code to be executed after a specified delay#>
    });

    <#delayInSeconds#>:从现在开始经过多少秒

    <#code to be executed after a specified delay#>:代码块

    示例程序(主线程):
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self delay2];
    }
    - (void)delay2
    {
        NSLog(@"It is begin.");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"open:birdmichael.com,The thread is%@",[NSThread currentThread]);
    });
        NSLog(@"it is next.");
    }
    输出:
    2015-08-03 01:05:05.445 GCD[88456:650588] It is begin.
    2015-08-03 01:05:05.446 GCD[88456:650588] it is next.
    2015-08-03 01:05:07.643 GCD[88456:650588] open:birdmichael.com,The thread is<NSThread: 0x7f86b9c28b80>{number = 1, name = main}
    示例程序(子线程):
    - (void)delay3
    {
        NSLog(@"It is begin.");
        // 获取全局队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), q, ^{
            NSLog(@"open:birdmichael.com,The thread is%@",[NSThread currentThread]);
    });
    
        NSLog(@"it is next.");
    }
    输出:
    2015-08-03 01:08:13.233 GCD[88947:653122] It is begin.
    2015-08-03 01:08:13.233 GCD[88947:653122] it is next.
    2015-08-03 01:08:15.234 GCD[88947:653225] open:birdmichael.com,The thread is<NSThread: 0x7ff60a524240>{number = 2, name = (null)}
    performSelector
    [self performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval)];
    selector:方法. object:传入的参数 delay:延时时间 示例代码:
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self delay1];
    }
    
    // 延时执行
    - (void)delay1
    {
        NSLog(@"It is begin.");
        [self performSelector:@selector(openMyWeb:) withObject:@"birdmichael.com" afterDelay:3];
        NSLog(@"it is next.");
    }
    -(void)openMyWeb:(NSString *)url
    {
        NSLog(@"open:%@,The thread is %@", url,[NSThread currentThread]);
    }
    输出:
    2015-08-03 00:53:47.945 GCD[86660:641006] It is begin.
    2015-08-03 00:53:47.946 GCD[86660:641006] it is next.
    2015-08-03 00:53:50.947 GCD[86660:641006] open:birdmichael.com,The thread is <NSThread: 0x7ff419c11c50>{number = 1, name = main}

    调度(队列)组

    有时候会出现需要等待多个耗时操作执行完毕以后,在执行后续的处理.

    通过下面的例子,来感受调度组的好处:

    不使用调度组,主线程串行执行:

    1.我们需要用3秒下载图片1

    2.我们需要用4秒下载图片2

    3.我们需要2秒把图片2画到图片1上.

    总结:我们需要通过9秒.

    不使用调度组,子线程并行执行:

    1.我们需要3秒下载图片1,需要4秒下载图片2

    2.判断是否图片1和图片2都下载完毕

    3.需要2秒把图片2画在图片1上.

    总结:我们只需要6秒.

    缺点:1.我们需要手动判断是否图片1和图片2都下载完毕了.2.整个代码执行过程中,可能会引入多个辅助桥接的变量.

    使用调度组,字线程并行执行:

    1.创建调度组

    2.把3秒下载图片1和4秒下载图片2丢入调度组

    3.获得调度组执行结束通知后,执行2秒吧图片2画在图片1上(自动)

    优点:我们不需要判断是否图片1和图片2是否都下载完毕了.

    示例代码:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
    // 并行执行的线程一
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
    // 并行执行的线程二
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
    // 汇总结果
    });
    手动管理运行状态(或计数)

    AFNetworking框架中会使用,了解就行,几乎不使用.

    dispatch_group_enter(group);

    dispatch_group_leave(group);

    提示:进入和退出group次数必须匹配,有进入就必须有退出

    示例代码:

    // 自动
    dispatch_group_async(group, queue, ^{ 
    
      // 执行代码
    
    }); 
    
    // 手动,等价于自动
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
    
      //执行代码
    
      dispatch_group_leave(group);
    
    });
    延伸阅读:[点击进入](http://stackoverflow.com/questions/10643797/wait-until-multiple-networking-requests-have-all-executed-including-their-comp/10644282#10644282) #### 一次性 有些变量只需要初始化一次(如从文件中读取配置参数,读取设备型号等等),可以使用dispatch_once来进行读取优化,保证只调用API一次,以后就只要直接访问变量即可.(**在单例模式中被广泛的使用**)
    void dispatch_once ( dispatch_once_t *predicate, dispatch_block_t block );
    该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
    dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
    同样直接使用代码块就好了.
    ![QQ20150803-2@2x](http://birdmichael.com/wp-content/uploads/2015/07/QQ20150803-2@2x.png)
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        <#code to be executed once#>
    });

    <#code to be executed once#>:只执行一次的代码块.

    示例代码:


    - (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0;i < 10; ++i){
    [self once1];
    }
    }
    -(void)once1
    {
    NSLog(@”< 重复测试 >”);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    NSLog(@”hello,birdmichael”);
    });
    }


    输出结果:
    2015-08-03 01:23:31.058 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.058 GCD[91307:661637] hello,birdmichael
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.059 GCD[91307:661637] < 重复测试 >
    2015-08-03 01:23:31.060 GCD[91307:661637] < 重复测试 >

     

    #### 重复执行

    重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
    void dispatch_apply ( size_t iterations, dispatch_queue_t queue, void (^block)(size_t) );

    iterations:重复次数, queue:队列 ,block:代码快

    #### 其他

    dispatch_time():延迟一定的时间后执行。

    dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)

    GCD还有很多其他用法,可以参考官方文档

    ### 总结(思维导图)

    GCD

     

    由于图片自动伸缩,建议点击图片查看原图,或保存到本地查看

    ## NSThread

    NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,也可以使用对象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,然后调用start方法启动线程。

    ### 创建并开启线程

    #### 动态方法

    优点:可以拿到线程设置一些属性,比如名字,优先级等

    缺点:需要手动启动线程.

    - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;


















    target



    线程执行的方法,这个selector最多只能接收一个参数



    selector



    selector消息发送的对象. 并且这个对象最多只有一个参数,并且没有返回值..



    argument



    传给selector的唯一参数,也可以是nil.


    示例代码:
    // 初始化线程
    NSThread thread = [[NSThread alloc] initWithTarget:self selector:@selector(downImage) object:nil];
    // 设置线程的优先级(0.0 - 1.0,1.0最高级)
    thread.threadPriority = 1;
    // 开启线程
    [thread start];


    #### 静态方法

    优点:会自动去开启线程,并且依据代码就可以了

    缺点:不能拿到线程,所以不能设置一些属性.

    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;


















    target



    线程执行的方法,这个selector最多只能接收一个参数



    selector



    selector消息发送的对象. 并且这个对象最多只有一个参数,并且没有返回值..



    argument



    传给selector的唯一参数,也可以是nil.


    示例代码:
    [NSThread detachNewThreadSelector:@selector(downImage) toTarget:self withObject:nil];
    // 调用完毕后,会马上创建并开启新线程


    #### 隐式方法

        [self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>]


        [self performSelector:<#(SEL)#> onThread:<#(NSThread )#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
    [self performSelector:<#(SEL)#> onThread:<#(NSThread )#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#> modes:<#(NSArray )#>]

    示例代码:
    [self performSelectorInBackground:@selector(downIamge) withObject:nil];


    ### 线程的执行状态

    #### 初始化状态(新建)

    实例化线程对象,但是并没有start.

    注意:使用静态方法和隐式方法跳过初始化状态.

    #### 就绪(确定执行)

    当线程对象发送了start方法,线程立马被加入可调度线程池中(是否执行完全依靠CPU控制,程序与是不可以控制的)

    #### 运行(执行中)

    此时CPU来负责调度可以调度的线程池中的线程.某一个时刻,执行的那个线程的状态就是执行中状态

    线程会在就绪与运行中自动快速切换(具体可以先理解上文提到的时间片)

    运行中的状态只有两种后续状态:1.阻塞2.完成

    #### 阻塞(取消)

    来到阻塞状态,说明此刻的线程已经移出了调度池(但是对象并不会销毁,还有加入调度池的可能性).

    一般来说阻塞会以下2种情况出现:1.调用了sleep.2.等待同步锁

    一般来说阻塞线程回到调度池也有2种情况:1.sleep到时.2.得到同步锁

    #### 死亡(完成)

    ##### 正常死亡

    线程执行完毕,会自动销毁当前线程

    ##### 非正常死亡

    异常:非正常情况导致(可能因为cpu,内存等不可知原因)

    强制退出:对象被终止(调用exit方法)
    + (void)exit

    注意:死亡对象是不可复活的(不可再跳转其他状态)

    ### 线程的sleep

    sleep方法是一个强制线程进入阻塞状态的方法.
    // 休眠指定时常:2s
    [NSThread sleepForTimeInterval:2];

    // 休眠到指定时间:从现在开始后的2s
    NSDate date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
    [NSThread sleepUntilDate:date];


    ### 线程的属性

    #### name

    线程名字.

    #### stackSize

    设置一条线程的大小,必须在开启之前执行.一般都不使用.

    提示:大小必须为4kb的倍数

    ### 线程的方法

    #### 获取当前线程

    返回当前线程的内存地址,线程名等等
    + (NSThread )currentThread;


    #### 获取主线程

    + (NSThread )mainThread


    ### 线程优先级服务质量

    + (double)threadPriority;
    + (BOOL)setThreadPriority:(double)p;

    @property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below

    @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started

    在IOS8以后使用服务质量:
    / The following Quality of Service (QoS) classifications are used to indicate to the system the nature and importance of work.  They are used by the system to manage a variety of resources.  Higher QoS classes receive more resources than lower ones during resource contention. /
    typedef NS_ENUM(NSInteger, NSQualityOfService) {
    /
    UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. /
    NSQualityOfServiceUserInteractive = 0x21,

    /
    UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list. /
    NSQualityOfServiceUserInitiated = 0x19,

    /
    Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results. This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator. This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained. For example, periodic content updates or bulk file operations such as media import. /
    NSQualityOfServiceUtility = 0x11,

    /
    Background QoS is used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work. For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. /
    NSQualityOfServiceBackground = 0x09,

    /
    Default QoS indicates the absence of QoS information. Whenever possible QoS information will be inferred from other sources. If such inference is not possible, a QoS between UserInitiated and Utility will be used. /
    NSQualityOfServiceDefault = -1
    } NS_ENUM_AVAILABLE(10_10, 8_0);


    ## 线程之间的通讯

    在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信

    ### 线程间通信的体现

    1个线程传递数据给另1个线程

    在1个线程中执行完特定任务后,转到另1个线程继续执行任务

    ### 线程间通信常用方法:NSOperation

        NSOperationQueue queue = [[NSOperationQueue alloc] init];

    [queue addOperationWithBlock:^{
    NSLog(@”耗时操作 %@”, [NSThread currentThread]);

    // 主线程更新 UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@”更新 UI %@”, [NSThread currentThread]);
    }];
    }];


    ### 线程间通信常用方法:NSThread

    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

    - (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(id)arg waitUntilDone:(BOOL)wait;


    ### 示例代码

    #### 初级体验

    - (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
    {

    NSLog(@”点击屏幕,确认开始执行–%@”,[NSThread currentThread]);
    // 串行异步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@”开始下载图片,%@”,[NSThread currentThread]);

    NSLog(@”提示’图片正在下载,请等待’,%@”,[NSThread currentThread]);
    NSURL
    url = [NSURL URLWithString:@”http://birdmichael.com/wp-content/uploads/2015/07/GCD1.png“];
    NSData date = [NSData dataWithContentsOfURL:url];
    UIImage
    image = [UIImage imageWithData:date];

    NSLog(@”图片下载完成,%@”,[NSThread currentThread]);

    // 主线程异步>因为涉及到UI
    dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@”设置控件的背景为需要下载的图片(%@)-%@”,image,[NSThread currentThread]);
    });
    });

    }

    输出:请重点观察时间(因为图片较大)
    2015-08-02 14:45:08.039 GCD[72660:594772] 点击屏幕,确认开始执行–<NSThread: 0x7fd820d02b00>{number = 1, name = main}
    2015-08-02 14:45:08.040 GCD[72660:594851] 开始下载图片,<NSThread: 0x7fd820d17740>{number = 2, name = (null)}
    2015-08-02 14:45:08.040 GCD[72660:594851] 提示’图片正在下载,请等待’,<NSThread: 0x7fd820d17740>{number = 2, name = (null)}
    2015-08-02 14:46:02.067 GCD[72660:594851] 图片下载完成,<NSThread: 0x7fd820d17740>{number = 2, name = (null)}
    2015-08-02 14:46:02.068 GCD[72660:594772] 设置控件的背景为需要下载的图片(<UIImage: 0x7fd820cf5f20>, {2297, 2417})-<NSThread: 0x7fd820d02b00>{number = 1, name = main}


    #### Sending Messages方法

    #import “ViewController.h”
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView iconView;
    @end

    @implementation YYViewController

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

    -(void)touchesBegan:(NSSet
    )touches withEvent:(UIEvent )event
    {

    // 在子线程中调用download方法下载图片
    [self performSelectorInBackground:@selector(download) withObject:nil];
    }
    -(void)download
    {
    //1.根据URL下载图片
    //从网络中下载图片
    NSURL
    urlstr=[NSURL URLWithString:@”http://birdmichael.com/wp-content/uploads/2015/07/GCD1.png“];

    //把图片转换为二进制的数据
    NSData data=[NSData dataWithContentsOfURL:urlstr];
    //把数据转换成图片
    UIImage
    image=[UIImage imageWithData:data];

    //2.回到主线程中设置图片
    [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
    }

    //设置显示图片
    -(void)settingImage:(UIImage )image
    {
    self.iconView.image=image;
    }


    #### Sending Messages方法延伸

    #import “ViewController.h”
    #import <NSData.h>

    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView iconView;
    @end

    @implementation YYViewController

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

    -(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
    {
    // 在子线程中调用download方法下载图片

    [self performSelectorInBackground:@selector(download) withObject:nil];
    }

    -(void)download
    {

    //1.根据URL下载图片
    //从网络中下载图片
    NSURL urlstr=[NSURL URLWithString:@”fdsf”];

    //把图片转换为二进制的数据
    NSData
    data=[NSData dataWithContentsOfURL:urlstr];//这一行操作会比较耗时

    //把数据转换成图片
    UIImage image=[UIImage imageWithData:data];

    //2.回到主线程中设置图片
    //第一种方式
    // [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];

    //第二种方式
    // [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];

    //第三种方式
    [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
    }

    //设置显示图片
    //-(void)settingImage:(UIImage
    )image
    //{
    // self.iconView.image=image;
    //}

    @end


    ### 延伸阅读

    官方文档:点击进入

    ## 线程同步(安全)

    说到多线程,我们在不断提高齐代码运行的性能的同时,因为操作过程中旺旺是很多个线程并发执行的,而这写线程极有可能去访问同一个资源(同一个对象,同一个变量,同一个文件),这个时候如果没有一个机制来控制,就很容易引发数据错乱和数据安全的问题.
    > 示例分析:每年春节都是一票难求,在12306买票的过程中,成百上千的票瞬间就消失了。不妨假设某辆车有1千张票,同时有几万人在抢这列车的车票,顺利的话前面的人都能买到票。但是如果现在只剩下一张票了,而同时还有几千人在购买这张票,虽然在进入购票环节的时候会判断当前票数,但是当前已经有100个线程进入购票的环节,每个线程处理完票数都会减1,100个线程执行完当前票数为-99,遇到这种情况很明显是不允许的。
    >
    >
    > 方案:如何解决呢?大家都上过公共厕所,如何避免不会出现正在方便的时候另外一个人走进来看到裸露的自己呢?对,你一进厕所,你就把厕所锁起来.其他人看到厕所锁起来,就代表这个厕所里有人,就只能等待你方便完毕.而你一出厕所,就把锁解开.其他人就可以顺利进入厕所.

    ### 锁的引入

    正如以上解决方案一样,锁的引入就是为了解决线程同步的方案.”线程同步”很像”同步调用”,其实我们可以把线程同步偏激的理解成:讲并行异步调用,因为同时访问统一资源,为了保证数据的安全,暂时转为同步.访问完资源后,再异步.

    注意:按理说,越安全约好,是不是我们所有的资源都应该锁住,保证齐安全呢?

    任何锁都是需要消耗大量的CPU资源的!所以,不是非必要情况都不用.

    QQ20150803-3@2x

    ### synchronized代码块(互斥锁)

    使用@synchronized是解决线程同步问题非常方便的一种方法,日常开发中也更推荐使用此方法。首先选择一个对象作为同步对象(也可以理解为锁对象)(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。下面的代码演示了如何使用@synchronized进行线程同步:
    @synchronized(self){
    if (_imageNames.count>0) {
    // 加锁代码
    }
    }

    注意:锁定1份代码只能有一把锁,用多把锁是无效的.(所以我们才会用self).

    互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁。
    > 提示:由于忽视锁对性能的效果太大,苹果不建议使用,所以没有自动提醒功能.

    ### 原子和非原子

    OC在定义属性时有nonatomic和atomic两种选择,前者为非原子,后者为原子.

    提示:原子采用的是”自旋锁”.

    #### atomic

    会自动为setter方法加锁(默认就是atomic)

    atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误.

    特点:线程安全,需要消耗大量的资源

    #### nonatomic

    特点:非线程安全,适合内存小的移动设备

    IOS开发建议:1.所有属性都声明为nonatomic,2.尽量避免多线程抢夺同一块资源(使用并发变成的目的就是为了提高程序性能,让更多的代码同时运行起来,达到并发运营的目的,而加入锁反而会让性能可能更低)

    如果万不得已:尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力.

    ### 自旋锁(spinlock)

    同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令或test_and_set指令实现;同互斥锁不同的是在锁操作需要等待的时候并不是睡眠等待唤醒,而是循环检测保持者已经释放了锁,这样做的好处是节省了线程从睡眠状态到唤醒之间内核会产生的消耗,在加锁时间短暂的环境下这点会提高很大效率

    #### 与互斥锁对比

    相同:

    都可以保证统一时间,只有一条线程执行锁定范围的代码

    不同:

    互斥锁:当发现有线程执行锁定的代码,当前线程会进入休眠状态,等待其他线程执行完毕,打开锁以后,当前线程会被唤醒.

    自旋锁:当发现有线程执行锁定的代码,当前线程会以死玄幻的方式,一直等到锁定代码执行完成

    所以:自旋锁更佳适合超轻量级锁定代码.

    ### 其他锁

    在iOS开发中,除了同步锁有时候还会用到一些其他锁类型,在此简单介绍一下:

    NSRecursiveLock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。

    NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。

    pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。

    rwlock:读写锁,高级别锁,区分读和写,符合条件时允许多个线程访问对象。处于读锁操作时可以允许其他线程和本线程的读锁, 但不允许写锁, 处于写锁时则任何锁操作都会睡眠等待;常见的操作系统会在写锁等待时屏蔽后续的读锁操作以防写锁被无限孤立而等待,在操作系统不支持情况下可以用引用计数加写优先等待来用互斥锁实现。 读写锁适用于大量读少量写的环境,但由于其特殊的逻辑使得其效率相对普通的互斥锁和自旋锁要慢一个数量级;值得注意的一点是按POSIX标准 在线程申请读锁并未释放前本线程申请写锁是成功的,但运行后的逻辑结果是无法预测

    ### 死锁

    互斥锁解决了竞态条件的问题,但很不幸同时这也引入了一些其他问题,其中一个就是死锁。当多个线程在相互等待着对方的结束时,就会发生死锁,这时程序可能会被卡住。

    比如下面的代码:
    dispatch_sync(_queue, ^{
    dispatch_sync(_queue, ^{
    //do something
    });
    })

    再比如:



    main() {
    dispatch_sync(dispatch_get_main_queue(), ^{
    //do something
    });
    }


    上面两个例子也可以说明 dispatch_sync 这个API是危险的,所以尽量不要用。

    当你的代码有死锁的可能时,它就会发生

    ### 资源饥饿

    当你认为已经足够了解并发编程面临的问题时,又出现了一个新的问题。锁定的共享资源会引起读写问题。大多数情况下,限制资源一次只能有一个线程进行读取访问其实是非常浪费的。因此,在资源上没有写入锁的时候,持有一个读取锁是被允许的。这种情况下,如果一个持有读取锁的线程在等待获取写入锁的时候,其他希望读取资源的线程则因为无法获得这个读取锁而导致资源饥饿的发生。

    ### 优先级反转

    优先级反转是指程序在运行时低优先级的任务阻塞了高优先级的任务,有效的反转了任务的优先级。GCD提供了3种级别的优先级队列,分别是Default, High, Low。 高优先级和低优先级的任务之间共享资源时,就可能发生优先级反转。当低优先级的任务获得了共享资源的锁时,该任务应该迅速完成,并释放掉锁,这样高优先级的任务就可以在没有明显延时的情况下继续执行。然而高优先级任务会在低优先级的任务持有锁的期间被阻塞。如果这时候有一个中优先级的任务(该任务不需要那个共享资源),那么它就有可能会抢占低优先级任务而被执行,因为此时高优先级任务是被阻塞的,所以中优先级任务是目前所有可运行任务中优先级最高的。此时,中优先级任务就会阻塞着低优先级任务,导致低优先级任务不能释放掉锁,这也就会引起高优先级任务一直在等待锁的释放。如下图:



    使用不同优先级的多个队列听起来虽然不错,但毕竟是纸上谈兵。它将让本来就复杂的并行编程变得更加复杂和不可预见。因此我们写代码的时候最好只用Default优先级的队列,不要使用其他队列来让问题复杂化。

    关于dispatch_queue的底层线程安全设计可参考:底层并发 API

    ## NSOperation

    ### 介绍

    使用NSOperation和NSOperationQueue进行多线程开发类似于GCD,但不像GCD是纯C语言的,这个是OC的.但相比较之下GCD会更快一些,但本质上NSOPeration其实就是GCD封装的.所以和GCD一样,只要将一个NSOperation(GCD操作)(实际开中需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue(GCD的队列)这个队列中线程就会依次启动.NSOperationQueue负责管理,执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系.

    ### NSOperation

    #### 介绍

    NSOperation是一個抽象的类,作用是可以讲代码和数据封装成任务,以便执行.(在GCD中,我们也把代码封装在一个block中).

    因为他时一个抽象类,所以必须需要一个继承的子类来使用,或者使用系统提供的两个子类:NSInvocationOperation或NSBlockOperation使用方法.

    你可以在多个操作(operation)中添加附属。你可以重用操作,取消或者暂停他们。NSOperation和 Key-Value Observation (KVO)是兼容的;例如,你可以通过监听NSNotificationCenter去让一个操作开始执行。

    #### NSOperation属性

    ##### 状态

    isCancelled - read-only property

    (是否取消,只读)

    isConcurrent - read-only property

    (是否并行,只读) // 已经被遗弃,使用Asynchronous代替,如果使用NSOperationQueue来控制NSOperation的话,沒有理由让他并行.

    isExecuting - read-only property

    (是否执行中,只读)

    isFinished - read-only property

    (是否完成,只读)

    isReady - read-only property

    (是否就绪,只读)

    isAsynchronous - read-only property

    (是否异步,只读) // ios 7 后被支持
    方法
    dependencies - read-only property

    (依赖关系,只读) // 但提供- (void)addDependency:增加方法 和 - (void)removeDependency:移除方法

    queuePriority - readable and writable property

    (队列优先级,读写)

    completionBlock - readable and writable property

    (完成后执行block,读写)



    #### operation同步vs异步

    ##### 同步

    可以在没有操作队列的前提下手动执行operation,但需要一些前提条件.尤其是operation对象必须准备好运行,并保证是调用start方法来执行.

    一个operation只有在其isReady方法返回YES时才被认为是可运行的.isReady方法会被整合进NSOperation的依赖管理系统来保证operation的依赖状态.只有在依赖关系清楚后,operation才开始运行.

    start方法在正式执行你得代码之前会做几个安全性检测.默认的start方法会生成operation依赖关系所需的KVO通知.同时保证已取消的operation不会再执行,以及在operation没就绪就开始运行时抛出异常.

    但是,只要NSBlockOperation封装的操作数 > 1,就会异步执行操作.
    > 但是,这样做是毫无意义的.因为直接执行代码.

    ##### 异步


    如果你想让某个操作异步执行,你只需要把操作丢人队列,程序会自动开启线程执行.

    或者还有一种情况,在NSBlockOperation如果使用+ (instancetype)blockOperationWithBlock:方法添加N个操作.再调用-(void)start方法,也会开启线程,并且主线程也会执行.

    NSInvocationOperation

    start

    start 方法 会在当前线程执行 @selector 方法,所以毫无意义,等价于直接self调用- (void)downloadImage:方法

    -(void)bmDemo1{
        /*创建一个调用操作
         object:调用方法参数
        */
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];
        //创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中
    //    [invocationOperation start];
    
    }
    - (void)downloadImage:(id)obj {
    
        NSLog(@"%@ %@", [NSThread currentThread], obj);
    }
    添加到队列

    将操作添加到队列,会异步执行 selector 方法

    - (void)bmDemo2 {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    
        [queue addOperation:invocationOperation];
    }
    添加多个操作

    会开启多条线程,而且不是顺序执行。与GCD中并发队列&异步执行效果一样!

    - (void)bmDemo3 {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        for (int i = 0; i < 5; ++i) {
            NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
    
            [queue addOperation:invocationOperation];
        }
    }
    结论

    操作 -> 异步执行的任务

    队列 -> 全局队列

    提示:一般不使用,因为考虑到效率和便捷角度,这个方法都没任何意义.

    NSBlockOperation

    NSBlockOperation比NSInvocationOperation更加灵活,并且不需要准备一个被调用的方法.

    将任务添加到操作
    - (void)bmDemo1 {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        NSBlockOperation *NSBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
        }];
    
        [queue addOperation:NSBlockOperation];
    }
    向队列中添加不同的操作
    - (void)opDemo5 {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"block %@", [NSThread currentThread]);
        }];
        [queue addOperation:operation1];
    
        NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
        [queue addOperation:operation2];
    }
    可以在同一个NSOperationQueue中添加任何一个NSOperation的子`类` > 提示:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作. #### #### 正确的响应Cancel事件 operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行 NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失 以下地方可能需要调用isCancelled: * 在执行任何实际的工作之前 * 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次 * 代码中相对比较容易中止操作的任何地方 示例代码
    - (void)demo1 
    {
        BOOL isDone = NO;
        while (![self isCancelled] && !isDone) {     
            // ..code..
            if([self isCancelled]){
                return ;      
            }
            // ..code..
        }
    }

    NSOperation依赖关系

    前面使用GCD很难在并行中控制线程的执行顺序,但是使用NSOperation就容易多了,每个NSOperation可以设置依赖线程.调用addDependency: 方法可以建立两个operation对象间的依赖关系.该方法创建了目标对象到当前对象之间的单向依赖关系.这个单向关系表明当前operation对象只有在目标operation对象执行完成之后才能执行.调用removeDependency:可以移除2个operation对象间的依赖关系.

    operation对象的依赖关系不限于同一个操作队列.有依赖关系的operation对象可以添加到不同的操作队列中.但是operation之间不能添加循环依赖关系(A依赖于B,B依赖于A).

    1359821609_3145

    operation的依赖关系建立在operation对象间KVO消息的发送.自定义的operation对象需要在自定义代码中的合适位置来添加KVO通知.

    换一句话说,设置依赖关系就是设置线程的执行顺序,并且采用蛇形结构,也就是说:我们想要先执行A,再执行B,最后执行C.那么就C依赖B,B依赖A.

    示例代码
    - (void)dependency {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"登录 %@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"软件付费 %@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"获取下载链接 %@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"通知用户正在下载中 %@", [NSThread currentThread]);
        }];
    
        [operation2 addDependency:operation1];
        [operation3 addDependency:operation2];
        [operation4 addDependency:operation3];
        // 注意不要循环依赖
        //    [operation1 addDependency:operation4]; // 严重错误!!!
    
        [queue addOperations:@[operation1, operation2, operation3] waitUntilFinished:NO];
        [[NSOperationQueue mainQueue] addOperation:operation4];
    
        NSLog(@"欢迎界面,让用户下载");
    }
    输出结果
    2015-08-07 05:33:06.812 NSThread[58353:1941467] 欢迎界面,让用户下载
    2015-08-07 05:33:06.812 NSThread[58353:1941581] 登录 <NSThread: 0x7f875acaf660>{number = 2, name = (null)}
    2015-08-07 05:33:06.820 NSThread[58353:1941581] 软件付费 <NSThread: 0x7f875acaf660>{number = 2, name = (null)}
    2015-08-07 05:33:06.821 NSThread[58353:1941581] 获取下载链接 <NSThread: 0x7f875acaf660>{number = 2, name = (null)}
    2015-08-07 05:33:06.822 NSThread[58353:1941467] 通知用户正在下载中 <NSThread: 0x7f875ac17380>{number = 1, name = main}

    其他方法

    Initialization
    初始化

    – init // Available in OS X v10.5 through OS X v10.9

    Executing the Operation
    执行工作

    – start

    – main

    – completionBlock

    – setCompletionBlock:

    Waiting for Completion
    等待执行

    – waitUntilFinished Instance Methods

     

    ### NSOperationQueue

    #### 介绍

    NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。



    #### NSOperationQueue属性


    operations - read-only property

    (操作组,只读)

    operationCount - read-only property

    (操作数量,只读)

    maxConcurrentOperationCount - readable and writable property

    (最大并行处理操作数(最大线程),读写)

    suspended - readable and writable property

    (是否暂停,读写)

    name - readable and writable property

    (名称,读写)



    #### 多核心的注意事項

    在多线程中使用单一的管理器(NSOperationQueue)是安全的,不需要创建锁去同步执行操作。

    队列中Operations的执行顺序

    对于添加到queue中的operations,它们的执行顺序取决于2点:

    1.首先看看NSOperation是否准备好了依赖关系.

    2.然后再根据所有NSOperation的相对优先级来确定.优先级等级则是operation对象本身的一个属性.默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级.优先级只能应用于相同queue中的operations.如果应用有多个operation queue,每个queue的优先级等级是互相独立的.因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行.

    注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序.先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行.(多个operations单纯设计优先级无法精准控制)

    最大并发操作数量

    队列的最大并发操作数量,意思是队列中最多同时运行几条线程,默认情况下是-1,也就是没有限制,同时运行队列中的全部操作(但不一定有多少操作,就会开启多少线程,具体开启多少线程由系统来决定).

    虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个Operation.setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量.设为1就表示queue每次只能执行一个操作.不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等.因此串行化的operation queue并不等同于GCD中的串行dispatch queue.

    示例代码

    // 每次只能执行一个操作  
    queue.maxConcurrentOperationCount = 1;  
    // 或者这样写  
    [queue setMaxConcurrentOperationCount:1];

    全局队列

    方便操作队列,同一冠以所有异步操作(可以有效避免多次创建)

    @property (nonatomic, strong) NSOperationQueue *queue;
    
    - (NSOperationQueue *)queue {
        if (_queue == nil) {
            _queue = [[NSOperationQueue alloc] init];
        }
        return _queue;
    }

    暂停和继续

    如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue.不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行.你可以在响应用户请求时,暂停一个queue来暂停等待中的任务.稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行

    比如用户滚动cell,立马暂停.滚动停止,立马恢复.

    // 暂停queue
    [queue setSuspended:YES];

    // 继续queue
    [queue setSuspended:NO];

    挂起和回复队列

    调用NSOperationQueue对象的setSuspended: 方法可以操作队列挂起.挂起队列不会阻碍当前正在执行中的operation,它只是阻止新的operation的执行.为了响应用户请求,你可以关起任何当前正在处理的工作,因为用户可能会最终重新恢复队列执行.

    取消Operations

    一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消.你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations方法取消当前queue中的所有操作.

    比如类存警告时,就可能会用到.

    示例代码

    // 取消单个操作  
    [operation cancel];  
    
    // 取消queue中所有的操作  
    [queue cancelAllOperations];

    等待Options完成

    为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情.如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成.通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验.绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待.阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。

    // 会阻塞当前线程,等到某个operation执行完毕
    [operation waitUntilFinished];

    除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法.注意:在等待一个 queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间.

    // 阻塞当前线程,等待queue的所有操作执行完毕
    [queue waitUntilAllOperationsAreFinished];

    提示:永远不能在主线程上等待一个operation结束。

    开发建议

    虽然可以向操作队列中添加任意数量的operation对象,但这么做其实是不现实的.如其它对象一样,NSoperation类的对象实例也是要消耗内存,并执行也会耗时.创建成千上万的完成小功能的operation对象,因过度派发和执行,会导致超出任务本身所需消耗的不良结果.如果应用程序本身的内存用量已很紧俏,那么在内存中包含了成千上万的operation对象会进一步降低你应用的性能.

    使用operation对象的关键在于,在任务工作量与保证计算机时刻处于繁忙状态之间找到一个合理的平衡点.尝试确保operation对象所完成工作的合理性.例如,同时创建100个完成同样工作的operation对象,则不如分10次,每次同时创建10个该operation对象.

    避免在同一个操作队列中一次添加大量的operation对象,避免添加operation的速度超过operation本身的执行速度的情况.一股脑的将所有operation对象添加到操作队列,则不如按批次添加.当一个批次完成之后,利用completion block来告诉应用程序来创建下一批次.有大量任务要处理时,尽量塞满操作队列会保证机器重复运转,但大量的operation对象会马上耗光你得内存.

    总而言之,在使用operation时,尽量在效率和时间之间找到平衡点.

    NSOperation.VS.GCD

    1. NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
    2. 在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
    3. NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
    4. GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
    5. 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
      使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。

    使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效.

    从编程原则来说,一般我们需要尽可能的使用高等级,封装完美的API,在必须时才使用底层API.当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择.

    RunLoop

    在cocoa中讲到多线程,那么就不得不讲到RunLoop。 在ios/mac的编码中,我们似乎不需要过多关心代码是如何执行的,一切仿佛那么自然。比如我们知道当滑动手势时,tableView就会滚动,启动一个NSTimer之后,timer的方法就会定时执行, 但是为什么呢,其实是RunLoop在帮我们做这些事情:分发消息。

    什么是RunLoop

    你应该看过这样的伪代码解释ios的app中main函数做的事情:

    int main(int argc, char * argv[])
    {
    while (true) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    }


    也应该看过这样的代码用来阻塞一个线程:

    while (!complete) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }


    或许你感觉到他们有些神奇,希望我的解释能让你明白一些.

    我们先思考一个问题: 当我们打开一个IOS应用之后,什么也不做,这时候看起来是没有代码在执行的,为什么应用没有退出呢?

    我们在写c的简单的只有一个main函数的程序时就知道,当main的代码执行完,没有事情可做的时候,程序就执行完毕退出了。而我们IOS的应用是如何做到在没有事情做的时候维持应用的运行的呢? 那就是RunLoop。

    RunLoop的字面意思就是“运行回路”,听起来像是一个循环。实际它就是一个循环,它在循环监听着事件源,把消息分发给线程来执行。RunLoop并不是线程,也不是并发机制,但是它在线程中的作用至关重要,它提供了一种异步执行代码的机制。

    事件源

    runloop

    由图中可以看出NSRunLoop只处理两种源:输入源、时间源。而输入源又可以分为:NSPort、自定义源、performSelector:OnThread:delay:, 下面简单介绍下这几种源:

    NSPort 基于端口的源

    Cocoa和 Core Foundation 为使用端口相关的对象和函数创建的基于端口的源提供了内在支持。Cocoa中你从不需要直接创建输入源。你只需要简单的创建端口对象,并使用NSPort的方法将端口对象加入到run loop。端口对象会处理创建以及配置输入源。

    NSPort一般分三种: NSMessagePort(基本废弃)、NSMachPort、 NSSocketPort。 系统中的NSURLConnection就是基于NSSocketPort进行通信的,所以当在后台线程中使用NSURLConnection 时,需要手动启动RunLoop, 因为后台线程中的RunLoop默认是没有启动的,后面会讲到。

    自定义输入源

    在Core Foundation程序中,必须使用CFRunLoopSourceRef类型相关的函数来创建自定义输入源,接着使用回调函数来配置输入源。Core Fundation会在恰当的时候调用回调函数,处理输入事件以及清理源。常见的触摸、滚动事件等就是该类源,由系统内部实现。

    一般我们不会使用该种源,第三种情况已经满足我们的需求

    performSelector:OnThread

    Cocoa提供了可以在任一线程执行函数(perform selector)的输入源。和基于端口的源一样,perform selector请求会在目标线程上序列化,减缓许多在单个线程上容易引起的同步问题。而和基于端口的源不同的是,perform selector执行完后会自动清除出run loop。

    此方法简单实用,使用也更广泛。

    定时源

    定时源就是NSTimer了,定时源在预设的时间点同步地传递消息。因为Timer是基于RunLoop的,也就决定了它不是实时的。

    RunLoop观察者

    我们可以通过创建CFRunLoopObserverRef对象来检测RunLoop的工作状态,它可以检测RunLoop的以下几种事件:

    • Run loop入口
    • Run loop将要开始定时
    • Run loop将要处理输入源
    • Run loop将要休眠
    • Run loop被唤醒但又在执行唤醒事件前
    • Run loop终止

    Run Loop Modes

    RunLoop对于上述四种事件源的监视,可以通过设置模式来决定监视哪些源。 RunLoop只会处理与当前模式相关联的源,未与当前模式关联的源则处于暂停状态。

    cocoa和Core Foundation预先定义了一些模式(Apple文档翻译):

    Mode

    Name

    Description

    Default

    NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)

    缺省情况下,将包含所有操作,并且大多数情况下都会使用此模式

    Connection

    NSConnectionReplyMode (Cocoa)

    此模式用于处理NSConnection的回调事件

    Modal

    NSModalPanelRunLoopMode (Cocoa)

    模态模式,此模式下,RunLoop只对处理模态相关事件

    Event Tracking

    NSEventTrackingRunLoopMode (Cocoa)

    此模式下用于处理窗口事件,鼠标事件等

    Common Modes

    NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)

    此模式用于配置”组模式”,一个输入源与此模式关联,则输入源与组中的所有模式相关联。



    我们也可以自定义模式,可以参考ASIHttpRequest在同步执行时,自定义了 runLoop 的模式叫ASIHTTPRequestRunLoopMode。ASI的Timer源就关联了此模式。