📚 开源资源分享——颜佐光的个人书架
✉️ 联系站长
书架 / Mac / swift

Swift 多线程

2021-06-08 14:24 · 约 6,733 字 · 浏览 2,155 次swift

Swift 多线程

容易混淆的术语:同步 异步 串行 并发

  • 同步: sync函数
    在当前线程中执行任务,不具备开启新线程的能力
  • 异步: async函数
    在新的线程中执行任务,具备开启新线程的能力
  • 同步和异步主要影响:能不能开启新的线程(是否在当前线程执行任务)
  • 并发和串行主要影响:任务的执行方式
    并发:多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务

sync和async用来控制是否要开启新的线程.队列的类型,决定了任务的执行方式(并发 串行). async只表示具有开启新线程的能力,但不一定开启新的线程.比如async传入主队列不会开启新的线程.主队列是在主线程执行.

  • sync同步函数向主队列添加任务会走造成死锁.

以下代码输出结果是什么?为什么?

override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.blue

        let queue = DispatchQueue.main
        queue.sync {
            print(1)
        }
  }

viewDidLoad方法本身就是主线程的一个任务.viewDidLoad这个任务是先添加进主线程的一个任务,需要先将viewDidLoad这个任务执行完,才能执行queue.sync任务.但queue.sync是后后添加的任务,需要等上一个任务viewDidLoad执行完才能执行,所以构成死锁.

  • 将sync函数换成async函数,还是在主队列,不会死锁,async函数不会等待任务执行完,会直接向下执行.async函数不要求立刻执行.async函数具备开启新现成的能力,但是在主线程执行任务,所以不会开启新的线程.
override func viewDidLoad() {
  super.viewDidLoad()
  view.backgroundColor = UIColor.blue
  print(1111)
  let queue = DispatchQueue.main
  queue.async {
    print(22222)
  }
  print(3333333)
}
//打印结果
1111
3333333
22222

自定义并发队列

override func viewDidLoad() {
  super.viewDidLoad()
  view.backgroundColor = UIColor.blue
  //这是一个并发队列
  let serialQueue = DispatchQueue.init(label: "", qos: .default, attributes: [.concurrent], autoreleaseFrequency: .inherit, target: nil)
  print(1111,Thread.current)
  serialQueue.async {
    print(2222,Thread.current)
    serialQueue.sync {
      print(33333,Thread.current)
    }
    print(4444444444,Thread.current)
    serialQueue.sync {
      print(555555555,Thread.current)
    }
    print(666666666,Thread.current)
  }
  print(77777777,Thread.current)
}
//打印结果
//注意:22的打印可能介于11和77之间,因为`async`函数不要求立刻执行,什么时候执行不确定.有可能22执行结束优先于777
1111 <NSThread: 0x600000d0a2c0>{number = 1, name = main}
77777777 <NSThread: 0x600000d0a2c0>{number = 1, name = main}
2222 <NSThread: 0x600000d88440>{number = 7, name = (null)}
33333 <NSThread: 0x600000d88440>{number = 7, name = (null)}
4444444444 <NSThread: 0x600000d88440>{number = 7, name = (null)}
555555555 <NSThread: 0x600000d88440>{number = 7, name = (null)}
666666666 <NSThread: 0x600000d88440>{number = 7, name = (null)}

自定义串行队列

override func viewDidLoad() {
  super.viewDidLoad()
  view.backgroundColor = UIColor.blue
  //这是一个并发队列
  let serialQueue = DispatchQueue(label: "自定义串行队列")
  print(11111)
  serialQueue.async {
    print(22222)
    //往串行队列中添加同步(立刻执行的任务会造成死锁)
    serialQueue.sync {
      print(3333)
    }
    print(4444)
  }
  print(5555)
}
//打印结果
//理论上22222的打印可能介于111和555之间
11111
5555
22222
4444
3333

死锁产生条件

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

RunLoop和多线程相关问题

如下代码输出什么?为什么?
class HomeViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let queue = DispatchQueue.global()
    queue.async {
      print(1)
      self.perform(#selector(self.test), with: nil, afterDelay: 0)
      print(2)
    }  
  }

  @objc func test() {
    print("test")
  }
}
//打印,没有看到test方法执行
1
2
  • perform(Selector, with: Any?, afterDelay: TimeInterval)本质是往RunLoop中添加定时器NSTimer,子线程默认没有启动RunLoop.
  • 如果想让上述代码工作,需要在子线程添加RunLoop并启动
  • perform(Selector, with: Any?, afterDelay: TimeInterval)是在runLoop中定义的方法.
  • self.perform(Selector!, with: Any!)底层是objc_messageSend
override func viewDidLoad() {
  super.viewDidLoad()
  view.backgroundColor = UIColor.blue
  let queue = DispatchQueue.global()
  queue.async {
    print(111)
    self.perform(#selector(self.test), with: nil, afterDelay: 0.0)
    print(333)
    //在子线程中添加runloop
    let port = Port()
    //perform(#selector(self.test), with: nil, afterDelay: 0.0)
    //方法已经在子线程的runloop中添加了NSTimer.所以不
    //需要再添加 port,所以这句代码可以去掉
    //runloop中只要有 source timer observer runloop就可以
    //成功运行
    RunLoop.current.add(port, forMode: .default)
    //RunLoop.current.run()
    RunLoop.current.run(mode: .default, before: Date.distantFuture)
  }

}
//打印结果
111
333
2222
下面代码执行结果是什么?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  let thread = Thread.init {
    print(1)
  }

  thread.start()
  self.perform(#selector(self.test), on: thread, with: nil, waitUntilDone: true)
}

@objc func test() {
  print(2)
}
  • 执行结果:打印1之后程序crash.因为线程执行thread.start()后,该线程的任务就执行完成,线程就被销毁了,销毁之后又使用该线程所以造成了crash.如果该线程里面启动了RunLoop该线程就不会销毁,就会正常执行.代码如下:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  let thread = Thread.init {
    print(1)
    //在runloop中添加source timer observer
    RunLoop.current.add(Port(), forMode: .default)
    //启动runloop
    RunLoop.current.run()
  }

  thread.start()
  self.perform(#selector(self.test), on: thread, with: nil, waitUntilDone: true)
}

@objc func test() {
  print(2)
}
  • runloop启动之后会等待该线程的任务.如果处理完该线程当前的任务,runloop就会进入休眠状态.等待下一个任务的到来,如果下一个任务到来runloop就会被激活.处理这个任务,当这个任务处理完成后runloop会再次进入休眠状态.

队列组的使用

  • 如何用GCD实现以下功能:异步并发执行任务1,任务2.等任务1和任务2执行完毕后,再回到主线程执行任务3.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  //创建队列组
  let group = DispatchGroup()
  //创建并发队列
  let queue = DispatchQueue.global()
  queue.async(group: group, execute: {
    for _ in 0...10{
      print(1,"任务1",Thread.current)
    }
  })
  queue.async(group: group, execute: {
    for _ in 0...10{
      print(2,"任务2",Thread.current)
    }
  })
  group.notify(queue: queue) {
    DispatchQueue.main.async {
      for _ in 0...10{
        print(3,"任务3",Thread.current)
      }
    }
  }
}
  • 可以看到任务1和任务2在子线程交替执行,执行完成后再执行任务3.

1

  • 最后回到主线程执行任务也可以通过队列组直接传入主队列或者通过主队列使用sync函数
group.notify(queue: DispatchQueue.main) {
  for _ in 0...10{
    print(3,"任务3",Thread.current)
  }
}
//或者
group.notify(queue: queue) {
  DispatchQueue.main.sync {
    for _ in 0...10{
      print(3,"任务3",Thread.current)
    }

  }
  • 如果想要办到任务1和任务2交替执行,等任务1和任务2都执行完成之后再交替执行任务3和任务4,那么任务3和任务4继续使用group.notify即可
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  //创建队列组
  let group = DispatchGroup()
  //创建并发队列
  let queue = DispatchQueue.global()

  queue.async(group: group, execute: {
    for _ in 0...5{
      print(1,"任务1",Thread.current)
    }
  })
  queue.async(group: group, execute: {
    for _ in 0...5{
      print(2,"任务2",Thread.current)
    }
  })
  group.notify(queue: queue) {
    for _ in 0...5{
      print(3,"任务3",Thread.current)
    }
  }
  group.notify(queue: queue) {
    for _ in 0...5{
      print(4,"任务4",Thread.current)
    }
  }
}

2

← 返回《Mac》目录书架 →