swift 多线程GCD实战经验

/ Mac / 没有评论 / 2000浏览

swift 多线程GCD实战经验

GCD (Grand Central Dispath)

回到主线程执行UI刷新

DispatchQueue.global().async {
  print("async do something\(Thread.current)")
  DispatchQueue.main.async {
    print("come back to main thread\(Thread.current)")
  }
}

延时执行

时间是可能会有延迟, 而且不能取消.

DispatchQueue.global(qos: .default).asyncAfter(deadline: DispatchTime.now() + 2.0) {
  print("time out")
}

如果有需要取消延时执行的业务,可以使用DispatchWorkItem,这个task能执行cancel.

let task = DispatchWorkItem { print("block which can cancel") }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5, execute: task)
task.cancel()

全局队列 的4个优先级qos

在GCD中,指定qos有以下两种方式

  1. 创建一个指定qos的queue
let globalQueue = DispatchQueue.global(qos: .default)
  1. 在提交block的时候,指定qos
queue.async(group: nil, qos: .background, flags: .inheritQoS) {
}

其中关于qos的关系,可以通过flags参数设置:

线程组DispatchGroup执行多个任务的业务逻辑

1. 简单的多线程任务逻辑:

//获取系统存在的全局队列
let queue = DispatchQueue.global(qos: .default)
//定义一个group
let group = DispatchGroup()
//并发任务,顺序执行
queue.async(group: group) {
  sleep(3)
  print("block1")
}
queue.async(group: group) {
  print("block2")
}
queue.async(group: group) {
  print("block3")
}
//1,所有任务执行结束汇总,不阻塞当前线程
group.notify(queue: .global(), execute: {
  print("group done")
})
//2,永久等待,直到所有任务执行结束,中途不能取消,阻塞当前线程
group.wait()
print("任务全部执行完成")
//打印
//block2
//block3
//block1
//group done
//任务全部执行完成

2. GCD里的Barrier和NSOperationQueue的dependency比较接近,C任务开始之前需要A任务完成,或者A和B任务完成。

let queue = DispatchQueue(label: "foo", attributes: .concurrent)
queue.async {
  print("A")
}
queue.async {
  print("B")
}
queue.async(flags: .barrier) {
  print("C")
}

3. 用group.enter()和group.leave()来解决带网络请求的线程间异步执行, 也可以使用下面介绍的信号量方式.

Dispatch队列会在闭包内的代码执行完的时候自动通知DispatchGroup. 但是如果你在Dispatch闭包里调用了一个异步请求,Dispatch内的代码很有可能在异步请求完成之前就跑完了,然后就告诉Group"这里的任务已经完成了", 这样会有业务问题.

//这对命令其实就相当于是在计数,当你enter()的时候,运行中的任务计数+1; 当你leave()的时候,运行中的任务-1
let group = DispatchGroup()
group.enter()
self.sendHTTPRequest1(params:[String: Any]) {
  print("request complete")
  group.leave()
}
group.enter()
self.sendHTTPRequest1(params:[String: Any]) {
  print("request complete")
  group.leave()
}
group.notify(queue: DispatchQueue.main) {
  print("all requests come back")
}

信号量 Semaphore 很多人不会读这个英语单词: [ˈseməfɔː(r)]

多个网络请求的多线程业务特用, 例如等待多个网络请求完成后再做某事, 特好用

//网络请求:
func network(success:()->(), failure:()->()){
}
//开启全局线程
DispatchQueue.global().async {
  //创建信号量
  let semaphore = DispatchSemaphore.init(value: 0)//初始值为0, 需要>0的时候wait函数才能收到通知
  //网络请求1
  network(success: {
    semaphore.signal()//网络请求结束, semaphore信号+1
  }) {
    semaphore.signal()//网络请求结束, semaphore信号+1
  }
  semaphore.wait()  //等待到semaphore的计数>0才收到通知往下执行, 执行后semaphore计数自动减去1
  //网络请求2
  network(success: {
    semaphore.signal()//网络请求结束, semaphore信号+1
  }) {
    semaphore.signal()//网络请求结束, semaphore信号+1
  }
  semaphore.wait()  //等待到semaphore的计数>0才收到通知往下执行执行后semaphore计数自动减去1
  
  print("两个网络请求都结束了, 此时semaphore计数器为0")
}

信号量对比DispatchGroup

import Foundation

print("Hello, World!")
//控制只下载3个资源
let semaphore = DispatchSemaphore(value: 3)
for i in 1...10 {
    DispatchQueue.global().async{
        semaphore.wait() // Semaphore计数-1
        print("正在下载第\(i)张图")
        // 模拟网络等待
        Thread.sleep(forTimeInterval: 2)
        print("第\(i)张图已下载")
        //semaphore.signal() // Semaphore计数+1 就能多下载一个
    }
}
RunLoop.main.run()

打印输出

Hello, World!
正在下载第2张图
正在下载第1张图
正在下载第3张图
第1张图已下载
第2张图已下载
第3张图已下载

多线程并发主要问题:

  1. 资源竞争
  2. 死锁
  3. 优先级倒置

资源竞争

线程1和2同时想要更新一个count: int这个属性

count += 1

这行代码仔细拆分开来会是这样子

  1. 加载count的值到CPU中
  2. 在CPU中将count+1
  3. 将新的 count 值写入内存中 img

解决方案

1. 串行线程访问

对于简单读写操作来说,串行Dispatch算是简单高效的方案.

private let queue = DispatchQueue(label: "...") // 默认串行线程
private var _count = 0
public var count: Int {
  get {
    return queue.sync {
      _count
    }
  } 
  set {
    queue.sync {
      _count = newValue
    }
  } 
}

2. Thread Barrier, Barrier后续的任务要在Barrier的内容执行完之后,才会运行。

private let safeQueue = DispatchQueue(label: "...",  attributes: .concurrent)
private var _count = 0 
public var count: Int {
  get {
    return safeQueue.sync {
      return _count
    }
  } 
  set {
    safeQueue.async(flags: .barrier) { 
      [unowned self] in
      self._count = newValue }
  } 
}

img 上面两种方案可以解决绝大部分业务问题,后面详解线程锁方案.

死锁的产生

sync函数往当前串行队列中添加任务就会造成死锁

//以下代码输出结果是什么?为什么?
override func viewDidLoad() {
  super.viewDidLoad()
  let queue = DispatchQueue.main
  queue.sync {
    print("我被锁死了😝")
  }
}
override func viewDidLoad() {
  super.viewDidLoad()
  print(1111)
  let queue = DispatchQueue.main
  queue.async {
    print(222)
  }
  print(3333)
}
//打印结果
1111
3333
2222