iOS多线程之超实用理论+demo演示(可下载)

背景简介

     在初学iOS相关知识过程当中,大多都对多线程有些恐惧的内心,同时感受工做中用上的几率不大。可是若是平时很少积累并学透多线程,当工做中真的须要用到的时候,就极可能简单百度后把一些知识点稀里糊涂地就用到工做中了,却不知里面有不少的坑,也有不少技巧须要在理论上先作了解,再结合实战,进一步去体会多线程的魅力和强大。html

     接下来,就对多线程来源的背景进行简单的介绍:ios

     在计算的早期,计算机能够执行的最大工做量是由 CPU 的时钟速度决定的。可是随着技术的进步和处理器设计的紧凑化,热量和其余物理约束开始限制处理器的最大时钟速度。所以,芯片制造商寻找其余方法来提升芯片的整体性能。他们决定的解决方案是增长每一个芯片上的处理器核心数量。经过增长内核的数量,一个单独的芯片能够每秒执行更多的指令,而不用增长 CPU 的速度或改变芯片的大小或热特性。惟一的问题是如何利用额外的内核。git

     应用程序使用多核的传统方法是建立多个线程。与依赖线程不一样,iOS 采用异步设计方法来解决并发问题。一般,这项工做涉及获取一个后台线程,在该线程上启动所需的任务,而后在任务完成时向调用方发送通知(一般经过一个回调函数)。github

     iOS 提供了一些技术,容许您异步执行任何任务,而无需本身管理线程。异步启动任务的技术之一是 Grand Central Dispatch (GCD)。这种技术采用线程管理代码,并将该代码移动到系统级别。您所要作的就是定义要执行的任务,并将它们添加到适当的分派队列中。GCD 负责建立所需的线程,并安排任务在这些线程上运行。因为线程管理如今是系统的一部分,GCD 提供了任务管理和执行的总体方法,比传统线程提供了更高的效率。编程

     OperationQueue(操做队列,api 类名为 NSOperationQueue )是 Objective-C 对象,是对 GCD 的封装。其做用很是相似于分派队列。您定义要执行的任务,而后将它们添加到 OperationQueue 中, OperationQueue 处理这些任务的调度和执行。与 GCD 同样, OperationQueue 为您处理全部线程管理,确保在系统上尽量快速有效地执行任务。swift

     接下来,就对如今工做中经常使用的这两种技术进行比较和实例解析。api

GCD、OperationQueue 对比

核心理念

  • GCD的核心概念:将 任务(block) 添加到队列,而且指定执行任务的函数。
  • NSOperation 的核心概念:把 操做(异步) 添加到 队列。

区别

  • GCD:安全

    • 将任务(block)添加到队列(串行/并发/主队列),而且指定任务执行的函数(同步/异步)
    • GCD是底层的C语言构成的API
    • iOS 4.0 推出的,针对多核处理器的并发技术
    • 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
    • 要中止已经加入 queue 的 block 须要写复杂的代码
    • 须要经过 Barrier(dispatch_barrier_async)或者同步任务设置任务之间的依赖关系
    • 只能设置队列的优先级
    • 高级功能:
      dispatch_once_t(一次性执行, 多线程安全);
      dispatch_after(延迟);
      dispatch_group(调度组);
      dispatch_semaphore(信号量);
      dispatch_apply(优化顺序不敏感大致量for循环);
  • OperationQueue:网络

    • OC 框架,更加面向对象,是对 GCD 的封装。数据结构

    • iOS 2.0 推出的,苹果推出 GCD 以后,对 NSOperation 的底层进行了所有重写。

    • 能够设置队列中每个操做的 QOS() 队列的总体 QOS

    • 操做相关
      Operation做为一个对象,为咱们提供了更多的选择:
      任务依赖(addDependency),能够跨队列设置操做的依赖关系;
      在队列中的优先级(queuePriority)
      服务质量(qualityOfService, iOS8+);
      完成回调(void (^completionBlock)(void)

    • 队列相关
      服务质量(qualityOfService, iOS8+);
      最大并发操做数(maxConcurrentOperationCount),GCD 不易实现;
      暂停/继续(suspended);
      取消全部操做(cancelAllOperations);
      KVO 监听队列任务执行进度(progress, iOS13+);

     接下来经过文字,结合实践代码(工程连接在文末)和运行效果 gif 图对部分功能进行分析。

GCD

队列

串行队列(Serial Queues)

     串行队列中的任务按顺序执行;可是不一样串行队列间没有任何约束; 多个串行队列同时执行时,不一样队列中任务执行是并发的效果。好比:火车站买票能够有多个卖票口,可是每一个排的队都是串行队列,总体并发,单线串行。

     注意防坑:串行队列建立的位置。好比下面代码示例中:在for循环内部建立时,每一个循环都是建立一个新的串行队列,里面只装一个任务,多个串行队列,结果总体上是并发的效果。想要串行效果,必须在for循环外部建立串行队列。

     串行队列适合管理共享资源。保证了顺序访问,杜绝了资源竞争。

      代码示例:

private func serialExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
        
        //串行队列,异步执行时,只开一个子线程
        let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
        
        for i in 0..<lArr.count{
            let lImgV = lArr[i]
            
            //清空旧图片
            lImgV.image = nil
            
         //注意,防坑:串行队列建立的位置,在这建立时,每一个循环都是一个新的串行队列,里面只装一个任务,多个串行队列,总体上是并行的效果。
            //            let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
            
            serialQ.async {
                
                print("第\(i)个 开始,%@",Thread.current)
                Downloader.downloadImageWithURLStr(urlStr: imageURLs[i]) { (img) in
                    let lImgV = lArr[i]
                    
                    print("第\(i)个 结束")
                    DispatchQueue.main.async {
                        print("第\(i)个 切到主线程更新图片")
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)个img is nil")
                    }
                }
            }
        }
    }

gif 效果图:

serialGCD
图中下载时可顺利拖动滚动条,是为了说明下载在子线程,不影响UI交互

log:

第0个 开始
第0个 结束
第1个 开始
第0个 更新图片
第1个 结束
第2个 开始
第1个 更新图片
第2个 结束
第3个 开始
第2个 更新图片
第3个 结束
第3个 更新图片

      由 log 可知: GCD 切到主线程也须要时间,切换完成以前,指令可能已经执行到下个循环了。可是看起来图片仍是依次下载完成和显示的,由于每一张图切到主线程显示都须要时间。

并发队列(Concurrent Queues)

     并发队列依旧保证中任务按加入的前后顺序开始(FIFO),可是没法知道执行顺序,执行时长和某一时刻的任务数。按 FIFO 开始后,他们之间不会相互等待。

     好比:提交了 #1,#2,#3 任务到并发队列,开始的顺序是 #1,#2,#3。#2 和 #3 虽然开始的比 #1 晚,可是可能比 #1 执行结束的还要早。任务的执行是由系统决定的,因此执行时长和结束时间都没法肯定。

     须要用到并发队列时,强烈建议 使用系统自带的四种全局队列之一。可是,当你须要使用 barrier 对队列中任务进行栅栏时,只能使用自定义并发队列。

Use a barrier to synchronize the execution of one or more tasks in your dispatch queue. When you add a barrier to a concurrent dispatch queue, the queue delays the execution of the barrier block (and any tasks submitted after the barrier) until all previously submitted tasks finish executing. After the previous tasks finish executing, the queue executes the barrier block by itself. Once the barrier block finishes, the queue resumes its normal execution behavior.

     对比:barrier 和锁的区别

  • 依赖对象不一样,barrier 依赖的对象是自定义并发队列,锁操做依赖的对象是线程。
  • 做用不一样,barrier 起到自定义并发队列中栅栏的做用;锁起到多线程操做时防止资源竞争的做用。

      代码示例:

private func concurrentExcuteByGCD(){
        let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
        
        for i in 0..<lArr.count{
            let lImgV = lArr[i]
            
            //清空旧图片
            lImgV.image = nil
            
            //并行队列:图片下载任务按顺序开始,可是是并行执行,不会相互等待,任务结束和图片显示顺序是无序的,多个子线程同时执行,性能更佳。
            let lConQ = DispatchQueue.init(label: "cusQueue", qos: .background, attributes: .concurrent)
            lConQ.async {
                print("第\(i)个开始,%@", Thread.current)
                Downloader.downloadImageWithURLStr(urlStr: imageURLs[i]) { (img) in
                    let lImgV = lArr[i]
                      print("第\(i)个结束")
                    DispatchQueue.main.async {
                        lImgV.image = img
                    }
                    if nil == img{
                        print("第\(i+1)个img is nil")
                    }
                }
            }
        }
    }

gif 效果图:
conGCD

log:

第0个开始,%@ <NSThread: 0x600002de2e00>{number = 4, name = (null)}
第1个开始,%@ <NSThread: 0x600002dc65c0>{number = 6, name = (null)}
第2个开始,%@ <NSThread: 0x600002ddc8c0>{number = 8, name = (null)}
第3个开始,%@ <NSThread: 0x600002d0c8c0>{number = 7, name = (null)}
第0个结束
第3个结束
第1个结束
第2个结束

串行、并发队列对比图

gcd-cheatsheet

注意事项

  • 不管串行仍是并发队列,都是 FIFO ;
    通常建立 任务(blocks)和加任务到队列是在主线程,可是任务执行通常是在其余线程(asyc)。须要刷新 UI 时,若是当前再也不主线程,须要切回主线程执行。当不肯定当前线程是否在主线程时,可使用下面代码:
/**
 Submits a block for asynchronous execution on a main queue and returns immediately.
 */
static inline void dispatch_async_on_main_queue(void (^block)()) {
    if (NSThread.isMainThread) {
        block();
    } else {
        dispatch_async(dispatch_get_main_queue(), block);
    }
}
  • 主队列是串行队列,每一个时间点只能有一个任务执行,所以若是耗时操做放到主队列,会致使界面卡顿。

  • 系统提供一个串行主队列,4个 不一样优先级的全局队列。
    用 dispatch_get_global_queue 方法获取全局队列时,第一个参数有 4 种类型可选:

    • DISPATCH_QUEUE_PRIORITY_HIGH
    • DISPATCH_QUEUE_PRIORITY_DEFAULT
    • DISPATCH_QUEUE_PRIORITY_LOW
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND
  • 串行队列异步执行时,切到主线程刷 UI 也须要时间,切换完成以前,指令可能已经执行到下个循环了。可是看起来图片仍是依次下载完成和显示的,由于每一张图切到主线程显示都须要时间。详见 demo 示例。

  • iOS8 以后,若是须要添加可被取消的任务,可使用 DispatchWorkItem 类,此类有 cancel 方法。

  • 应该避免建立大量的串行队列,若是但愿并发执行大量任务,请将它们提交给全局并发队列之一。建立串行队列时,请尝试为每一个队列肯定一个用途,例如保护资源或同步应用程序的某些关键行为(如蓝牙检测结果须要有序处理的逻辑)。

block(块)相关

     调度队列复制添加到它们中的块,并在执行完成时释放块。
     虽然队列在执行小任务时比原始线程更有效,可是建立块并在队列上执行它们仍然存在开销。若是一个块执行的工做量太少,那么内联执行它可能比将它分派到队列中要便宜得多。判断一个块是否工做量太少的方法是使用性能工具为每一个路径收集度量数据并进行比较。
     您可能但愿将 block 的部分代码包含在 @autoreleasepool 中,以处理这些对象的内存管理。尽管 GCD 调度队列拥有本身的自动释放池,但它们不能保证这些池什么时候耗尽。若是您的应用程序是内存受限的,那么建立您本身的自动释放池可让您以更有规律的间隔释放自动释放对象的内存。

dispatch_after

     dispatch_after 函数并非在指定时间以后才开始执行处理,而是在指定时间以后将任务追加到队列中。这个时间并非绝对准确的。
  代码示例:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2s后执行");
    });

dispatch_semaphore

      在多线程访问可变变量时,是非线程安全的。可能致使程序崩溃。此时,能够经过使用信号量(semaphore)技术,保证多线程处理某段代码时,后面线程等待前面线程执行,保证了多线程的安全性。使用方法记两个就好了,一个是wait(dispatch_semaphore_wait),一个是signal(dispatch_semaphore_signal)。

具体请参考文章Semaphore回顾

dispatch_apply

     当每次迭代中执行工做与其余全部迭代中执行的工做不一样,且每一个循环完成的顺序不重要时,能够用 dispatch_apply 函数替换循环。注意:替换后, dispatch_apply 函数总体上是同步执行,内部 block 的执行类型(串行/并发)由队列类型决定,可是串行队列易死锁,建议用并发队列。

原循环:

for (i = 0; i < count; i++) {
   printf("%u\n",i);
}
printf("done");

优化后:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
 //count 是迭代的总次数。
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n",i);
});

//一样在上面循环结束后才调用。
printf("done");

     您应该确保您的任务代码在每次迭代中完成合理数量的工做。与您分派到队列的任何块或函数同样,调度该代码以便执行会带来开销。若是循环的每次迭代只执行少许的工做,那么调度代码的开销可能会超过将代码分派到队列可能带来的性能优点。若是您在测试期间发现这一点是正确的,那么您可使用步进来增长每一个循环迭代期间执行的工做量。经过大步前进,您能够将原始循环的多个迭代集中到一个块中,并按比例减小迭代次数。例如,若是您最初执行了 100次 迭代,但决定使用步长为 4 的迭代,那么您如今从每一个块执行 4 次循环迭代,迭代次数为 25次 。

自问自答

  • 一个队列的不一样任务能够在多个线程执行吗?
    答:串行队列,异步执行时,只开一个子线程;无所谓多个线程执行;
    并发队列,异步执行时,会自动开多个线程,能够在多个线程并发执行不一样的任务。

  • 一个线程能够同时执行多个队列的任务吗?
    答:一个线程某个时间点只能执行一个任务,执行完毕后,可能执行到来自其余队列的任务(若是有的话)。好比:主线程除了执行主队列中任务外,也可能会执行非主队列中的任务。

    队列与线程关系示例图:
    queues & threads

  • qualityOfService 和 queuePriority 的区别是什么?
    答:
    qualityOfService:
         用于表示 operation 在获取系统资源时的优先级,默认值:NSQualityOfServiceBackground,咱们能够根据须要给 operation 赋不一样的优化级,如最高优化级:NSQualityOfServiceUserInteractive。
    queuePriority:
         用于设置 operation 在 operationQueue 中的相对优化级,同一 queue 中优化级高的 operation(isReady 为 YES) 会被优先执行。
         须要注意区分 qualityOfService (在系统层面,operation 与其余线程获取资源的优先级) 与 queuePriority (同一 queue 中 operation 间执行的优化级)的区别。同时,须要注意 dependencies (严格控制执行顺序)与 queuePriority (queue 内部相对优先级)的区别。

  • 添加依赖后,队列中网络请求任务有依赖关系时,任务结束断定以数据返回为准仍是以发起请求为准?
    答:以发起请求为准。分析过程详见NSOperationQueue队列中操做依赖相关思考

OperationQueue

  • NSOperation
         NSOperation 是一个"抽象类",不能直接使用。抽象类的用处是定义子类共有的属性和方法。NSOperation 是基于 GCD 作的面向对象的封装。相比较 GCD 使用更加简单,而且提供了一些用 GCD 不是很好实现的功能。是苹果公司推荐使用的并发技术。它有两个子类:

    • NSInvocationOperation (调用操做)
    • NSBlockOperation (块操做)
           通常经常使用NSBlockOperation,代码简单,同时因为闭包性使它没有传参问题。任务被封装在 NSOperation 的子类实例类对象里,一个 NSOperation 子类对象能够添加多个任务 block 和 一个执行完成 block ,当其关联的全部 block 执行完时,就认为操做结束了。
  • NSOperationQueue
          OperationQueue也是对 GCD 的高级封装,更加面向对象,能够实现 GCD 不方便实现的一些效果。被添加到队列的操做默认是异步执行的。

PS:常见的抽象类有:

  • UIGestureRecognizer
  • CAAnimation
  • CAPropertyAnimation

能够实现 非FIFO 效果

经过对不一样操做设置依赖,或优先级,可实现 非FIFO 效果。
  代码示例:

func testDepedence(){
        let op0 = BlockOperation.init {
            print("op0")
        }
        
        let op1 = BlockOperation.init {
            print("op1")
        }
        
        let op2 = BlockOperation.init {
            print("op2")
        }
        
        let op3 = BlockOperation.init {
            print("op3")
        }
        
        let op4 = BlockOperation.init {
            print("op4")
        }
        
        op0.addDependency(op1)
        op1.addDependency(op2)
        
        op0.queuePriority = .veryHigh
        op1.queuePriority = .normal
        op2.queuePriority = .veryLow
        
        op3.queuePriority = .low
        op4.queuePriority = .veryHigh
        
        gOpeQueue.addOperations([op0, op1, op2, op3, op4], waitUntilFinished: false)
    }

log:

op4
 op2
 op3
 op1
 op0

op4
 op3
 op2
 op1
 op0

说明:操做间不存在依赖时,按优先级执行;存在依赖时,按依赖关系前后执行(与无依赖关系的其余任务相比,依赖集合的执行顺序不肯定)

队列暂停/继续

经过对队列的isSuspended属性赋值,可实现队列中未执行任务的暂停和继续效果。正在执行的任务不受影响。

///暂停队列,只对未执行中的任务有效。本例中对串行队列的效果明显。并发队列因4个任务一开始就很容易一块儿开始执行,即便挂起也没法影响已处于执行状态的任务。
    @IBAction func pauseQueueItemDC(_ sender: Any) {
        gOpeQueue.isSuspended = true
    }
    
    ///恢复队列,以前未开始执行的任务会开始执行
    @IBAction func resumeQueueItemDC(_ sender: Any) {
       gOpeQueue.isSuspended = false
    }

gif 效果图:
pauseResume

取消操做

  • 一旦添加到操做队列中,操做对象实际上归队列全部,不能删除。取消操做的惟一方法是取消它。能够经过调用单个操做对象的 cancel 方法来取消单个操做对象,也能够经过调用队列对象的 cancelAllOperations 方法来取消队列中的全部操做对象。
  • 更常见的作法是取消全部队列操做,以响应某些重要事件,如应用程序退出或用户专门请求取消,而不是有选择地取消操做。

取消单个操做对象

取消(cancel)时,有 3 种状况:
1.操做在队列中等待执行,这种状况下,操做将不会被执行。
2.操做已经在执行中,此时,系统不会强制中止这个操做,可是,其 cancelled属性会被置为 true 。
3.操做已完成,此时,cancel 无任何影响。

取消队列中的全部操做对象

方法: cancelAllOperations。一样只会对未执行的任务有效。
demo 中代码:

deinit {
        gOpeQueue.cancelAllOperations()
        print("die:%@",self)
    }

自问自答

  • 经过设置操做间依赖,能够实现 非FIFO 的指定顺序效果。那么,经过设置最大并发数为 1 ,能够实现指定顺序效果吗?
    A:不能够!
    设置最大并发数为 1 后,虽然每一个时间点只执行一个操做,可是操做的执行顺序仍然基于其余因素,如操做的依赖关系,操做的优先级(依赖关系比优先级级别更高,即先根据依赖关系排序;不存在依赖关系时,才根据优先级排序)。所以,序列化 操做队列 不会提供与 GCD 中的序列 分派队列 彻底相同的行为。若是操做对象的执行顺序对您很重要,那么您应该在将操做添加到队列以前使用 依赖关系 创建该顺序,或改用 GCD 的 串行队列 实现序列化效果。

  • Operation Queue的 block 中为什么无需使用 [weak self] 或 [unowned self] ?
    A:即便队列对象是为全局的,self -> queue -> operation block -> self,的确会形成循环引用。可是在队列里的操做执行完毕时,队列会自动释放操做,自动解除循环引用。因此没必要使用 [weak self] 或 [unowned self] 。
    此外,这种循环引用在某些状况下很是有用,你无需额外持有任何对象就可让操做自动完成它的任务。好比下载页面下载过程当中,退出有循环引用的界面时,若是不执行 cancelAllOperation 方法,能够实现继续执行剩余队列中下载任务的效果。

func addOperation(_ op: Operation)
Discussion:
Once added, the specified operation remains in the queue until it finishes executing.
Declaration

func addOperation(_ block: @escaping () -> Void)
Parameters
block
The block to execute from the operation. The block takes no parameters and has no return value.
Discussion
This method adds a single block to the receiver by first wrapping it in an operation object. You should not attempt to get a reference to the newly created operation object or determine its type information.

  • 操做的 QOS 和队列的 QOS 有何关系?
    A:队列的 QOS 设置,会自动把较低优先级的操做提高到与队列相同优先级。(原更高优先级操做的优先级保持不变)。后续添加进队列的操做,优先级低于队列优先级时,也会被自动提高到与队列相同的优先级。
    注意,苹果文档以下的解释是错误的 This property specifies the service level applied to operation objects added to the queue. If the operation object has an explicit service level set, that value is used instead.
    缘由详见:Can NSOperation have a lower qualityOfService than NSOperationQueue?

常见问题

如何解决资源竞争问题

资源竞争可能致使数据异常,死锁,甚至因访问野指针而崩溃。

  • 对于有明显前后依赖关系的任务,最佳方案是 GCD串行队列,能够在不使用线程锁时保证资源互斥。
  • 其余状况,对存在资源竞争的代码加锁或使用信号量(初始参数填1,表示只容许一条线程访问资源)。
  • 串行队列同步执行时,若是有任务相互等待,会死锁。
    好比:在主线程上同步执行任务时,因任务和以前已加入主队列但未执行的任务会相互等待,致使死锁。
func testDeadLock(){
        //主队列同步执行,会致使死锁。block须要等待testDeadLock执行,而主队列同步调用,又使其余任务必须等待此block执行。因而造成了相互等待,就死锁了。
        DispatchQueue.main.sync {
            print("main block")
        }
        print("2")
    }

可是下面代码不会死锁,故串行队列同步执行任务不必定死锁

- (void)testSynSerialQueue{
    dispatch_queue_t myCustomQueue;
    myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);
     
    dispatch_async(myCustomQueue, ^{
        printf("Do some work here.\n");
    });
     
    printf("The first block may or may not have run.\n");
     
    dispatch_sync(myCustomQueue, ^{
        printf("Do some more work here.\n");
    });
    printf("Both blocks have completed.\n");
}

如何提升代码效率

“西饼传说”

代码设计优先级:系统方法 > 并行 > 串行 > 锁,简记为:西饼传说

  • 尽量依赖 系统 框架。实现并发性的最佳方法是利用系统框架提供的内置并发性。
  • 尽早识别系列任务,并尽量使它们更加 并行。若是由于某个任务依赖于某个共享资源而必须连续执行该任务,请考虑更改体系结构以删除该共享资源。您能够考虑为每一个须要资源的客户机制做资源的副本,或者彻底消除该资源。
  • 不使用锁来保护某些共享资源,而是指定一个 串行队列 (或使用操做对象依赖项)以正确的顺序执行任务。
  • 避免使用 GCD 调度队列操做队列 提供的支持使得在大多数状况下不须要锁定。

肯定操做对象的适当范围

  • 尽管能够向操做队列中添加任意大量的操做,但这样作一般是不切实际的。与任何对象同样,NSOperation 类的实例消耗内存,而且具备与其执行相关的实际成本。若是您的每一个操做对象只执行少许的工做,而且您建立了数以万计的操做对象,那么您可能会发现,您花在调度操做上的时间比花在实际工做上的时间更多。若是您的应用程序已经受到内存限制,那么您可能会发现,仅仅在内存中拥有数万个操做对象就可能进一步下降性能。
  • 有效使用操做的关键是 在你须要作的工做量和保持计算机忙碌之间找到一个适当的平衡 。尽可能确保你的业务作了合理的工做量。例如,若是您的应用程序建立了 100 个操做对象来对 100 个不一样的值执行相同的任务,那么能够考虑建立 10 个操做对象来处理每一个值。
  • 您还应该避免将大量操做一次性添加到队列中,或者避免连续地将操做对象添加到队列中的速度快于处理它们的速度。与其用操做对象淹没队列,不如批量建立这些对象。当一个批处理完成执行时,使用完成块告诉应用程序建立一个新的批处理。当您有不少工做要作时,您但愿保持队列中充知足够的操做,以便计算机保持忙碌,可是您不但愿一次建立太多操做,以致于应用程序耗尽内存。
  • 固然,您建立的操做对象的数量以及在每一个操做对象中执行的工做量是可变的,而且彻底取决于您的应用程序。你应该常用像 Instruments 这样的工具来帮助你在效率和速度之间找到一个适当的平衡。有关 Instruments 和其余可用于为代码收集度量标准的性能工具的概述,请参阅 性能概述

术语解释摘录

  • 异步任务(asynchronous tasks):由一个线程启动,但实际上在另外一个线程上运行,利用额外的处理器资源更快地完成工做。
  • 互斥(mutex):提供对共享资源的互斥访问的锁。
    互斥锁一次只能由一个线程持有。试图获取由不一样线程持有的互斥对象会使当前线程处于休眠状态,直到最终得到锁为止。
  • 进程(process):应用软件或程序的运行时实例。
    进程有本身的虚拟内存空间和系统资源(包括端口权限) ,这些资源独立于分配给其余程序的资源。一个进程老是包含至少一个线程(主线程) ,而且可能包含任意数量的其余线程。
  • 信号量(semaphore):限制对共享资源访问的受保护变量。
    互斥(Mutexes)和条件(conditions)都是不一样类型的信号量。
  • 任务(task),表示须要执行的工做量。
  • 线程(thread):进程中的执行流程。
    每一个线程都有本身的堆栈空间,但在其余方面与同一进程中的其余线程共享内存。
  • 运行循环(run loop): 一个事件处理循环,
    接收事件并派发到适当的处理程序。

官方并发编程词汇表

本文 demo 地址

MultiThreadDemo

参考文章

Concurrency Programming Guide
iOS Concurrency: Getting Started with NSOperation and Dispatch Queues

下节预告

文中提到的知识点,“与其用操做对象淹没队列,不如批量建立这些对象。当一个批处理完成执行时,使用完成块告诉应用程序建立一个新的批处理”,在最近的工做中的确有须要相似的需求,等有时间会进行总结,就做为下一篇文章的预告吧。

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

相关文章
相关标签/搜索