Swift学习47:多线程

张建 lol

前言

为什么几乎所有的GUI框架都是单线程的?:处理问题代价大于收益

多线程可以做什么

  • 网络请求
  • IO:读写文件
  • 计算
  • 数据模型转换
  • ….

多线程编程的方式

  • Thread
  • Operation 和 OperationQueue
  • GCD

Thread

  • 三种中最轻量级的,自己管理线程的生命周期和线程同步
  • 线程同步对数据加锁有一定的系统开销
  1. 快捷方式创建
  • detachNewThread(_ block: @escaping @Sendable () -> Void)
1
2
3
4
5
6
7
8
9
10
11
import Foundation
import PlaygroundSupport

// 不希望程代码在主线完成后退出,因为很多线程需要异步操作
PlaygroundPage.current.needsIndefiniteExecution = true

for i in 0...10 {
Thread.detachNewThread {
print(i)
}
}
  • detachNewThreadSelector(_ selector: Selector, toTarget target: Any, with argument: Any?)
  1. 初始化器
  • Thread(target: selector: , object: )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
import PlaygroundSupport

// 不希望程代码在主线完成后退出,因为很多线程需要异步操作
PlaygroundPage.current.needsIndefiniteExecution = true

class ObjectThread {
func threadTest() {
let thread = Thread(target: self, selector: #selector(threadMethod), object: nil)
// 手动开启
thread.start()
}
@objc func threadMethod() {
print("threadMethod")
}
}
// 实例
let obj = ObjectThread()
obj.threadTest()

Operation 和 OperationQueue 概述

  • 面向对象
  • Operation + OperationQueue
  • 取消、依赖、任务优先级、复杂逻辑、保存业务状态、子类化

Operation

  • Operation:抽象类,任务
  • BlockOperation
  1. 状态:
  • isReady:准备好了,可以执行了
  • isExecuting:执行中
  • isFinished:执行完成
  • isCancelled:取消执行
  1. 同步、异步
  • sync
  • async
  1. 添加依赖

OperationQueue

  • OperationQueue 队列,可以加入很多 Operation
  • 底层使用 GCD
  • maxConcurrentOperationCount:可以设置最大并发数
  • defaultMaxConcurrentOperationCount:根据当前系统条件动态确定的最大并发数,建议使用这个
  • 可以取消所有的 Operation,但是当前正在执行的不会取消
  • 所有 Operation 执行完毕后退出销毁

BlockOperation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ObjectOperation {
func test() {
let operation = BlockOperation{ [weak self] in
self?.threadMethod()
}
let queue = OperationQueue()
queue.addOperation(operation)
}
@objc func threadMethod() {
sleep(1)
print("threadMethod")
}
}
let op = ObjectOperation()
op.test()
print("after invoke test")

=====
after invoke test
threadMethod

由打印结果可知,异步执行了

继承 Operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ObjectOperation {
func test() {
let operation = MyOpetation()
let queue = OperationQueue()
queue.addOperation(operation)
}
@objc func threadMethod() {
sleep(1)
print("threadMethod")
}
}

class MyOpetation: Operation {
override func main() {
sleep(1)
print("myOperation main")
}
}

let op = ObjectOperation()
op.test()
print("after invoke test")

========
after invoke test
myOperation main

Operation 完成的回调

  • completionBlock
1
2
3
4
5
6
7
8
operation.completionBlock = { () -> Void in
print("completionBlock")
}

========
after invoke test
myOperation main
completionBlock

GCD

  • 任务 + 队列
  • 易用
  • 效率
  • 性能

GCD功能

  • 创建管理Queue
  • 提交Job
  • Dispatch Group
  • 管理Dispatch Object
  • 信号量Semaphore
  • 队里屏障Barrier
  • Dispatch Source
  • Queue Context数据
  • Dispatch I/O Channel
  • Dispatch Data 对象

GCD队列

  • 主队列:任务在主线程执行
  • 并行队列:任务会以先进先出的顺序入列和出列,但是因为多个任务可以并行执行,所以顺序是不一定的
  • 串行队列:任务会以先进先出的顺序入列和出列,但是同一时刻只会执行一个任务

GCD-队列API

  • Dispatch.main
  • Dispatch.global
  • DispatchQueue
  • queue.label
  • setTarget

GCD 基本操作

  • sync

提交任务到当前队列里,并且直接任务执行完成,当前队列才会返回

  • async

调用一个任务去立即执行,但是不用等任务执行完当前队列就会返回

  • asyncAfter

调度一个任务多久之后去执行,但是不用等任务执行完当前队列就会返回

GCD 串行 & 并行

  • 串行和并行描述的是任务之间如何运行
  • 串行任务每一个仅执行一个
  • 并行任务可以多个同时执行

GCD 同步 & 异步

GCD 使用

  • 同步
1
2
3
4
5
6
7
8
9
10
let queue = DispatchQueue(label: "myQueue",qos: DispatchQoS.default,attributes: DispatchQueue.Attributes.concurrent,autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit,target: nil)
queue.sync { // 同步
sleep(1)
print("in queue sync")
}
print("after invoke queue method")

======
in queue sync
after invoke queue method
  • 异步
1
2
3
4
5
6
7
8
9
queue.async { // 异步
sleep(1)
print("in queue async")
}
print("after invoke queue method")

=====
after invoke queue method
in queue async
  • asyncAfter
1
2
3
4
5
6
7
8
queue.asyncAfter(deadline: .now() + 1) {
print("in asyncAfter")
}
print("after invoke queue method")

=======
after invoke queue method
in asyncAfter

GCD 源码剖析

  • dispatch_sync

    • 线程A在串行队里dq中执行task1的过程中,如果再向dq中投递串行任务task2,同时还要求必须阻塞当前线程,等待task2结束(sync投递task2),那么这时候会发生死锁

    • 因为这时候task1还没有结束,串行队列不会去执行task2,而我们又要在当前线程等待task2的结束才肯继续执行task1,即task1在等待task2,而task2也在等待task1,循环等待,形成死锁

1
2
3
4
5
6
7
8
9
// 串行队列
let queue = DispatchQueue(label: "label")
queue.async { // 异步
print("in queue async") // 串行队列执行task1
queue.sync {
print("in queue sync") // 串行队列加入task2
}
}
// 死锁

临界区

一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码

竟态条件

  • 两个或多个线程读写某些数据,而最后的结果取决于线程运行的精确时序

Locks

SpinLock

  • 线程通过busy-wait-loop方式来获取锁,任何时刻只有一个线程能够获取锁,其他线程忙等待知道获取锁

  • 临界区尽量简短,控制在100行代码以内,不要有限时或隐士的系统调用,调用的函数也尽量简短

  • 保证访问锁的线程全部都处于同一优先级

@synchronized

1
2
3
4
5
func synchronized(_ obj: Any, closure: ()->()) {
objc_sync_enter(obj)
closure()
objc_sync_exit(obj)
}
  • 只有传同样的对象给synchronized,才能起到加锁的作用
  • 如果传nil,无法起到加锁的作用
  • 可以重入
  • synchronized不会持有传给它的对象

典型场景

  • 一个页面有三个网络请求,需要在三个网络请求都返回的时候刷新页面

  • 实现一个多线程安全的Array的读和写

  • 编写一个多线程下载器,可以执行多个下载任务,每个任务可以保存当前下载字节数,总字节数,可以设置回调得到当前下载进度

  • 需要在主线程等待一个异步任务返回,才继续执行下面的逻辑,但是又不希望堵塞用户事件

【场景一】:一个页面有三个网络请求,需要在三个网络请求都返回的时候刷新页面

【场景二】:实现一个多线程安全的Array的读和写

一个队列加方法方法

  • 1、首先是 并行队列,我们需要保持多线程并行操作
  • 2、sync 方法,封装读操作,读操作在调用读方法时能直接拿到返回值,而不是异步获取
  • 3、async 方法使用 barrier flag封装写操作,起到一个栅栏的作用,等待所有的 barrier flag 函数前操作执行完成后, barrier flag 函数之后的所有操作才执行

【场景三】:编写一个多线程下载器,可以执行多个下载任务,每个任务可以保存当前下载字节数,总字节数,可以设置回调得到当前下载进度

【场景四】:需要 在主线程等待一个异步任务返回,才继续执行下面的逻辑,但是又不希望堵塞用户事件

Promise 多线程编程模式

  • 所谓的 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理

  • 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成)、Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英文意思就是 承诺,表示其他手段无法改变

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 变为 Rejected,只要这两种情况发送,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发送了,你再对 Resolved 对象添加回调函数,也会立即得到这个结果。这与事件 Event 完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的

Pipeline

  • 将一个任务分解为若干个阶段(State),前阶段的输出作为下阶段的输入,各个阶段由不同的工作者线程负责执行

  • 各个任务的各个阶段是并行(Parallel)处理的

  • 具体任务的处理是串行的,即完成一个任务要一次执行各个阶段,但从整体任务上看,不同任务的各个阶段的执行时并行的

Master/Slave

将一个任务分解为若干个语义等同的子任务,并由专门的工作者线程来并行执行这些子任务,既提高计算效率,又实现了信息隐藏

  • Post title:Swift学习47:多线程
  • Post author:张建
  • Create time:2023-03-12 22:52:33
  • Post link:https://redefine.ohevan.com/2023/03/12/Swift课程/Swift学习47:多线程/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.