一般的结构化编程吸取了Goto式非结构化编程的经验,通过控制流遵守通用的约定,建立编程结构。
而并发的发展历程也同上基本一致。
(资料图片仅供参考)
由于多线程开发中的长耗时特征,在大型系统中,非结构化的并发编程在运行时跟踪单元操作、遭遇内存冲突时遇到的挑战更大,更难以解决,也就更迫切地需要一套编程理论来作为共识性的指导,也就有了结构化并发。
结构化并发
其可以概括为:
『即使进行并发操作,也要保证控制流路径的单一入口和单一出口。程序可以产生多个控制流来实现并发,但是所有的并发路径在出口时都应该处于完成 (或取消) 状态,并合并到一起。』
好处是:使抽象层得以有效运作。在这一结构下编码时要可以拍着胸脯保证代码不会跳转到结构外,是严格『自包含』的。
同步与异步
找不同!
是的,
和字面意思相反,同步函数返回、完成之前,运行它的线程无法执行其他操作。
同步 synchronous异步 asynchronous
UIKit和SwiftUI都不是线程安全的:
对于用户输入的处理和UI的绘制,必须在与主线程绑定的
main runloop
中进行。为避免长耗时操作将主线程堵塞导致用户输入无响应、UI卡顿,最常见的应对办法是将同步操作转换成异步操作:
开辟后台线程运行长耗时操作,然后在操作结束时提供运行在主线程的回调。
串行与并行
两者都是执行操作的顺序或方式;同步操作一定以串行方式执行。 串行:同一时刻的多个线程之间一定是为同一个操作使用,不会另作其他事,甚至只在同一个线程内逐条执行。异步操作则主要以并行方式执行,线程间通信时会以串行方式执行,也就是既能并行也能串行。 并行:同一时刻的多个线程一并执行多个操作。为降低理解并发的难度,提并发时,一般都限定在『异步和并行代码的组合』这个简化版的意义。
并发编程的困难大致上有两个:
如何确保顺序执行;————逻辑正确性
如何确保运算资源调度、访问不冲突。————内存安全性
异步函数
为了化解并发编程的如上两个困难,引入异步函数
func loadSignature() async throws -> String {
fatalError("暂未实现")
}
在返回箭头前加上 async关键字就可以把函数声明为异步函数。
与 throw关键字类似地,该关键字帮助编译器确保两件事:
它允许在函数体内部(正确地)使用 await关键字;
它要求其他来源在调用这个函数时,使用 await 关键字。
await代表了函数在此处可能会放弃当前线程,它是程序的潜在暂停点。
Task 任务
对于同步函数来说,线程决定了它的执行环境。
对于异步函数来说,任务决定了它的执行环境。
『Swift 并发编程中,结构化并发需要依赖异步函数,而异步函数又必须运行在某个任务上下文中,因此可以说,想要进行结构化并发,必须具有任务上下文。
实际上,Swift 结构化并发就是以任务为基本要素进行组织的。』
简单地使用 就可以让我们获取一个任务执行的上下文环境,它接受 `async` 标记的闭包。而且能继承当前任务上下文的优先级等特性,创建一个新的任务树的根节点。我们可以在其中使用异步函数,用法类似于 Task { 异步代码 }。
> Task的要点
> 创建、组织、检查和取消任务
> 一个任务具有它自己的优先级和取消标识,它可以拥有若干个子任务并在其中执行异步函数。
> 无论是正常完成还是抛出错误,子任务会将结果向上报告给父任务,在所有子任务完成之前 (不论是正常结束还是抛出),父任务是不会完成的。
> 当一个父任务被取消时,这个父任务的取消标识将被设置,并向下传递到所有的子任务中去。
Task Group 任务组
在任务运行上下文中,或具体来说——
在某个异步函数中,我们可以通过await withTaskGroup 为当前的任务添加一组结构化的并发子任务。
这些子任务在被添加后立刻开始执行,最终在离开group的作用域时再汇集到一起。
async let 异步绑定
类似于 let地, async let定义一条局部的不变变量,并通过等号右侧的表达式来初始化该不变变量。
不同的是,该初始化表达式必须是异步函数的调用,通过将其绑定到该不变变量上,在当前 Task上下文创建一道并发执行的子任务并立刻加以执行。
async let赋值之后将立刻执行,即使在 await之前执行就已经完成,其结果依然可以等到 await语句时再进行求值。
和 新建一个任务根节点不同,async let所创建的子任务是任务树上的叶子节点。
任务组和异步绑定的对比
async let可以理解成任务组如 withTaskGroup的语法糖,但是异步绑定并不能动态地表达任务的数量。
『一个大致的使用原则是,如果我们需要比较「严肃地」界定结构化并发的起始,那么用任务组 Task Group 的闭包将它限制起来,并发的结构会显得更加清晰;
而如果我们只是想要快速地并发开始少数几个任务,并减少其他模板代码的干扰,那么使用 async let 进行异步绑定,会让代码更简洁易读。』
标签: