Swift3.0多线程编程之GCD

作者: 2e919d99a871 | 来源:发表于2017-09-11 16:49 被阅读329次

    前言: 多线程编程对于一个iOS开发者来说非常重要。本文只针对GCD展开探讨,旨在应对一切开发中或者面试中遇到的GCD问题。

    1.多线程的基本概念

    线程:
    (thread)是组成进程的子单元,操作系统的调度器可以对线程进行单独的调度。实际上,所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)。

    单核与多核CPU操作系统下多线程的不同:
    多线程可以在单核 CPU 上同时(或者至少看作同时)运行。操作系统将小的时间片分配给每一个线程,这样就能够让用户感觉到有多个任务在同时进行。如果 CPU 是多核的,那么线程就可以真正的以并发方式被执行,从而减少了完成某项操作所需要的总时间。

    线程生命周期:
    1. 新建:实例化线程对象
    2. 就绪: 向线程对象发送star消息,线程对象被加入可调度线程池中等待CPU调度。
    3. 运行: CPU负责调度线程池中可调度线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化都是由CPU负责,程序员不能干预
    4. 阻塞: 当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)
    5. 死亡: 正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中中止执行/在主线程中止线程对象。还有线程的 exit和cancel.
    6. Thread.exit 一旦强行终止线程,后续的所有代码都不会执行。
    7. thread.cancel取消,并不会直接取消线程,只是给线程对象添加 isCancelled 标记

    2. 线程的安全问题

    多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
    解决方案:
    互斥锁(同步锁)
    @syncchronized(锁对象) {
    //需要锁定的代码
    }
    判断的时候锁对象要存在,如果代码中只有一个地方需要加锁,大多都使用self作为锁对象,这样可以避免单独再创建一个锁对象。
    加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。
    自旋锁
    加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。

    属性修饰atomic就是一把自旋锁。

    nonatomic: 非原子属性,同一时间可以有很多线程读和写。非线程安全的,不过效率更高,一般使用nonatomic。
    atomic: 原子属性(线程安全),保证同一时间只有一个线程能够写入(但是同一时间多个线程都可以取值),需要耗费大量资源。

    3.GCD的理解和使用

    GCD的特点:
    GCD会自动利用更多的CPU内核
    GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等)
    程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码

    GCD的基本概念:
    任务(block):任务就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。
    同步(sync):一个接着一个,前一个没有执行完,后面不能执行,不开线程
    异步(async):开启多个新线程,任务同一时间可以一起执行。异步是多线程的代名词
    队列:装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列
    并发队列:线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)
    串行队列:线程只能依次有序的执行
    下面是代码:
    由于篇幅限制,就不贴执行结果了,如果有兴趣不妨自己写来看看

    //串行同步:任务在主线程上按顺序执行,不能开子线程
        func serialSync() {
            let queue1 = DispatchQueue(label: "com.ittmom1.www")
            let queue2 = DispatchQueue(label: "com.ittmom2.www")
            queue1.sync {
                for i in 0 ..< 10 {
                    print("🔴",i,Thread.current)
                }
            }
            queue1.sync {
                for i in 100 ..< 110 {
                    print("🔴",i,Thread.current)
                }
            }
            
            queue2.sync {
                for i in 1000 ..< 1010 {
                    print("🔵",i,Thread.current)
                }
            }
                    
        }
    
    //串行异步,一个队列可以开一条子线程,
    //同一个队列任务按顺序执行,不同队列
    //的任务可异步执行
    func serialAsync() {
            let queue1 = DispatchQueue(label: "com.ittmom1.www")
            let queue2 = DispatchQueue(label: "com.ittmom2.www")
            
            queue1.async {
                for i in 0 ..< 10 {
                    print("🔴",i,Thread.current)
                }
            }
            
            queue1.async {
                for i in 100 ..< 110 {
                    print("🔴",i,Thread.current)
                }
            }
            
            queue2.async {
                for i in 1000 ..< 1010 {
                    print("🔵",i,Thread.current)
                }
            }
            
        }
    
    //并发同步,不能开新线程,所有任务按顺序在主线程完成
    //结论: 同步执行的任务所在的队列,不管是串行还是并发
    //都不能开启新线程(所以这并没有什么卵用)
        func concurrentSync() {
            let queue1 = DispatchQueue(label: "com.ittmom1.www",attributes: .concurrent)
            let queue2 = DispatchQueue(label: "com.ittmom2.www",attributes: .concurrent)
            queue1.sync {
                for i in 0 ..< 10 {
                    print("🔴",i,Thread.current)
                }
            }
            queue1.sync {
                for i in 100 ..< 110 {
                    print("🔴",i,Thread.current)
                }
            }
            
            queue2.sync {
                for i in 1000 ..< 1010 {
                    print("🔵",i,Thread.current)
                }
            }
            
            queue2.sync {
                for i in 10000 ..< 10010 {
                    print("🔵",i,Thread.current)
                }
            }
        }
    
    //并发异步: 不同队列并发执行
    //一条队列可以同时开启多条线程,同一队列多个不同任务异步执行
        func concurrentAsync() {
            let queue1 = DispatchQueue(label: "com.ittmom1.www",attributes: .concurrent)
            let queue2 = DispatchQueue(label: "com.ittmom2.www",attributes: .concurrent)
            queue1.async {
                for i in 0 ..< 10 {
                    print("🔴",i,Thread.current)
                }
            }
            queue1.async {
                for i in 100 ..< 110 {
                    print("🔴",i,Thread.current)
                }
            }
            
            queue2.async {
                for i in 1000 ..< 1010 {
                    print("🔵",i,Thread.current)
                }
            }
        }
    

    GCD基础部分总结(可以说,看懂了这里,再也不用发愁面试被问到同步异步的区别了):
    串行队列同步执行,不开启线程(主线程中执行任务),代码从上往下按顺序执行。
    串行队列异步执行,每个队列可以开启一条线程,相同队列的不同任务串行执行。不同队列可以实现异步执行。
    并发队列同步执行,不开启线程(主线程中执行任务),代码从上往下按顺序执行。
    并发队列异步执行,同一个队列可以开启多条线程,线程可以同时执行多个任务。

    4. GCD的高级用法(精华)

    4.1 利用initiallyInactive手动触发执行任务

    var inactiveQueue: DispatchQueue?
        
    func concurrentQueue() {
    let anotherQueue = DispatchQueue.init(label: "anotherQueue", qos: .utility, attributes: [.initiallyInactive, .concurrent])
    inactiveQueue = anotherQueue
       
    anotherQueue.async {
      for i in 0 ..< 10 {
          //sleep(1)
          print("queue1 (1)",i, Thread.current)
      }
    }
       
    anotherQueue.async {
      for i in 100 ..< 110 {
          //sleep(1)
          print("queue1 (2)",i, Thread.current)
      }
    }
       
    anotherQueue.async {
      for i in 1000 ..< 1010 {
          //sleep(1)
          print("queue1 (3)",i, Thread.current)
      }
      
    }
       
       
    }
    override func viewDidAppear(_ animated: Bool) {
    print("viewDidAppear")
    super.viewDidAppear(animated)
    concurrentQueue()
    
    if let queue = inactiveQueue {
      queue.activate()
    }
    }
    

    4.2 DispatchGroup的使用
    如果想在dispatch_queue中所有任务执行完成之后再做某些操作,可以使用DispatchGroup。

    let group = DispatchGroup()
       
    let queue1 = DispatchQueue(label: "task1")
    queue1.async(group: group){
      for _ in 1 ..< 10 {
          sleep(1)
          let date = NSDate()
          print("🔴",Thread.current,date)
      }
    }
       
    let queue2 = DispatchQueue(label: "task2")
    queue2.async(group: group) {
      self.delay(2, { 
          print("🔵",Thread.current)
      })
    }
       
    group.notify(queue: DispatchQueue.main) {
      let date = NSDate()
      print("task1,task2任务完成",date)
    }
    

    wait: 如果组里有多个并发队列存在,调用wait可以在任务执行完毕以后再继续执行代码,wait可以阻塞当前线程,等待执行结果。

    let group = DispatchGroup()
    let queue1 = DispatchQueue.init(label: "com.ittmom1.www", qos: .userInitiated, attributes: .concurrent)
    let queue2 = DispatchQueue.init(label: "com.ittmom2.www", qos: .userInitiated, attributes: .concurrent)
       
    queue1.async(group: group) {
      sleep(2)
      NSLog("队列一任务执行完成",Thread.current)
    }
       
    queue2.async(group: group) {
      sleep(1)
      NSLog("队列二任务执行完成", Thread.current)
    }
       
    NSLog("等待前")
    //这里wait方法返回值是 DispatchTimeoutResult ,是一个枚举
    // success: 在指定时间内完成任务
    // timedOut: 超过指定时间完成任务,即 超时
    let result = group.wait(timeout:.now() + .seconds(3))
       
    NSLog("等待后")
       
    NSLog("队列外任务执行", Thread.current)
       
    group.notify(queue: DispatchQueue.main) {
      switch result {
      case .success: NSLog("任务完成,没有超时",Thread.current)
      case .timedOut: NSLog("任务完成,超时",Thread.current)
      }
    }
    执行结果: 
    2017-09-11 10:50:32.765867+0800 Dispatch_Test[4480:4020080] 等待前
    2017-09-11 10:50:33.774021+0800 Dispatch_Test[4480:4020114] 队列二任务执行完成
    2017-09-11 10:50:34.767320+0800 Dispatch_Test[4480:4020080] 等待后
    2017-09-11 10:50:34.767569+0800 Dispatch_Test[4480:4020080] 队列外任务执行
    2017-09-11 10:50:34.781383+0800 Dispatch_Test[4480:4020113] 队列一任务执行完成
    2017-09-11 10:50:34.810031+0800 Dispatch_Test[4480:4020080] 任务完成,超时
    

    4.3 Semaphore 信号量
    用于控制资源被多次访问的情况,保证线程安全的统计数量。
    举个栗子: 一个女神可以同时和两个备胎谈恋爱,如果有第三个备胎想插进来,需要等其中一个备胎没钱了,养不起女神了,才能轮得到你。当然,这没有前后顺序,谁不行了,谁撤...

    let semaphore = DispatchSemaphore.init(value: 2)
            
    let queue1 = DispatchQueue.init(label: "semaphore1", qos: .userInitiated, attributes: .concurrent)
       
    queue1.async {
      semaphore.wait()
      print("第一个备胎和女神嘿嘿嘿")
      sleep(1)
      print("第一个备胎没钱了,可以滚了")
      semaphore.signal()
    }
       
    queue1.async {
      semaphore.wait()
      print("第二个备胎和女神嘿嘿嘿")
      sleep(2)
      print("第二个备胎没钱了,可以滚了")
      semaphore.signal()
    }
       
    queue1.async {
      semaphore.wait()
      print("终于轮到老子了")
      semaphore.signal()
    }
    执行结果:
    第一个备胎和女神嘿嘿嘿
    第二个备胎和女神嘿嘿嘿
    第一个备胎没钱了,可以滚了
    终于轮到老子了
    第二个备胎没钱了,可以滚了
    

    4.4 Barrier
    保证一个队列里的前一个任务执行完毕以后,才执行后面的任务。
    举个栗子: 就拿翟欣欣骗婚的事情说,欣欣童鞋要通过世纪佳缘网骗老实人和她结婚,然后一边搜集老实人的把柄,一边跟公安局的舅舅密谋勒索老实人的财产。但是欣欣童鞋只可能保证跟老实人结婚以后才能做后续的工作,所以,这时候我们要用到Barrier

    let queue1 = DispatchQueue.init(label: "semaphore1", qos: .userInitiated, attributes: .concurrent)
       
    queue1.async(flags: .barrier) {
      print("通过世纪佳缘网寻找老实人")
      
      sleep(2)
      print("和老实人结婚")
    }
       
    queue1.async {
      print("搜集老实人的把柄")
      
      sleep(2)
      print("找到老实人漏税和灰色经营的证据")
    }
       
    queue1.async {
      print("联系公安局的舅舅")
      
      sleep(2)
      print("和舅舅一起敲诈勒索老实人")
    }
    
    执行结果:
    通过世纪佳缘网寻找老实人
    和老实人结婚
    搜集老实人的把柄
    联系公安局的舅舅
    找到老实人漏税和灰色经营的证据
    和舅舅一起敲诈勒索老实人
    

    4.5 延时执行

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time, execute: {
        //执行任务
    })
    

    5. 写在最后

    看完此文,面试无忧
    本文参考链接:
    Swift 3 中的 GCD 与 Dispatch Queue
    GCD精讲(Swift 3)

    本文所有有关代码(如果帮到你了,也请帮忙点个star):
    demo地址

    相关文章

      网友评论

      本文标题:Swift3.0多线程编程之GCD

      本文链接:https://www.haomeiwen.com/subject/zrrfsxtx.html