当前位置: 首页 > news >正文

Swift 初阶 —— Sendable 协议 data races

一、data races

1、什么是 data races

官方文档对 data races 定义的解释是:

意思就是说 data races 就是多线程间没有同步地访问可变变量. 换句话说, data races 的定义就是: 在同时有读线程和写线程的情况下, 多线程间没有以串行的方式去访问同一块连续内存.

2、data races 是如何产生的

关于 data races 的产生原因, Swift 的官方文档是这样解释的:

总体意思就是说: 如果已经有一个线程在写这块内存, 但此时又有另一个线程访问这块内存内存, 那么就会产生 data races.

3、data races 例子

import Foundationfinal class Counter: @unchecked Sendable { // 为了消 warning 才用的 @unchecked Sendable, 但其实 value 是线程不安全的, 会造成 data racesvar value = 0
}let counter = Counter()
let queue = DispatchQueue.global()for _ in 0..<10_000 {queue.async {// 这里没有任何同步保护,多线程同时读写 value// 很容易出现 data race,导致最终值小于 10_000counter.value += 1}
}// 简单等待一下所有任务(非常粗糙,只是为了示例)
Thread.sleep(forTimeInterval: 1.0)
print("最终结果:\(counter.value)")

结果小于 10000 的原因是这样的, 假如只有两个线程, 且 counter.value 的值为 9997:

counter.value 并发执行过程
步骤线程 A线程 B
1读 counter.value, 寄存器a 值为 9997
2读 counter.value, 寄存器b 值为 9997
3寄存器a 执行 +1 操作, 此时寄存器a 值为 9998
4寄存器b 执行 +1 操作, 此时寄存器a 值为 9998
5把寄存器a 的值写进 counter.value, 此时 conuter.value 为 9998
6把寄存器b 的值写进 counter.value, 此时 conuter.value 为 9998

于是这就导致了 counter.value 本应为 9999 的, 经过两次 +1 后, 变成了 9998.

二、 concurrency domain

1、什么是 concurrency domain

根据 Swift 的官方文档, 它是这样定义 concurrency domain 的:

中文翻译就是: 在一个 task 或 actor 的内部, 包含可变参数或可变属性的那部分代码就是一个 concurrency domain. 

举个 🌰:

@MainActor
final class ViewModel: ObservableObject {@Published var message: String = "准备中…"func loadData() {// 当前方法在 MainActor 隔离域内执行Task {// 切换到后台隔离域执行耗时工作let text = await fetchRemoteText()// 回到 MainActor 更新 UIself.message = text}}private func fetchRemoteText() async -> String {// detached task 将作业放到独立的 concurrency domainawait Task.detached(priority: .background) {try? await Task.sleep(nanoseconds: 1_000_000_000) // 模拟网络延迟return "数据加载完成"}.value}
}

因为 ViewModel 是一个 Actor, 且 ViewModel 内有个可变的变量 (message) , 所以 ViewModel 就是一个 concurrency domain.

2、什么是 concurrency domain 间的传递

简单来说就是通过调 actor 函数或闭包的方式把某个可变变量或属性传进一个 actor 中执行, 又或是把某个变量或属性通过闭包捕获被某个 Task 或 actor 的闭包访问, 那么就属于跨 concurrency domain 传递.

举个🌰:

// e.g. 普通 Task 捕获值(MainActor 域 → Task 域)
struct Score: Sendable {var value: Int
}func startWork() {let score = Score(value: 42)      // 创建于 MainActor(主线程)Task.detached { [score] in// 这里进入 Task 的并发域,score 被安全传递进来print("当前分数:\(score.value)")}
}// e.g. 在不同 actor 间传递
actor DataStore {private var items: [Int] = []func add(value: Int) {items.append(value)}func takeSnapshot() -> [Int] {   // [Int] 默认符合 Sendableitems}
}struct Report: Sendable {let values: [Int]
}func fetchReport(store: DataStore) async -> Report {let snapshot = await store.takeSnapshot()// snapshot 从 DataStore 的 actor 域,传到调用者所在的并发域return Report(values: snapshot)
}

三、Sendable 协议

1、Sendable 协议用处

sendable 就是为了确保这个类的数据是线程安全的. 也就是说, 如果一个类遵循 sendable, 那么这个类只能被多个 task 以串行的方式访问.

2、如何遵循 Sendable 协议

2.1. 隐式遵循

符合以下情况的 Swift 会自动 (隐式) 认为它遵循 sendable:

  • 所有 Actor 自动遵循 sendable
  • 所有基本值类型 (Array, Int, Dict……) 都遵循 sendable
  • 所有存储属性 (包括闭包) 都遵循 sendable 的 Struct
  • 所有关联值类型都遵循 sendable 的 Enum
  • class.
    • 该 class 不是某个类 (除 NSObject) 的子类, 或 class 只继承了 NSObject
    • 该 class 的所有存储属性 (包括闭包) 都遵循 sendable
    • 该 class 为 final class

2.2. @unchecked Sendable & Mutex

2.2.1. @unchecked Sendable

因为 Sendable 的目的是确保同一块内存被多个 Task 串行访问, 而不是被并发访问; 因此我们可以在一个类里用 gcd 或锁实现内存的串行访问, 来达到 Sendable 的目的. 但 DispatchQueue 和 NSLock 都是引用类型, 且不遵循 Sendable, 因此 Swift 会认为这个类肯定不遵循 Sendable. 那在跨 concurrency domain 的时候如何解决呢? 其中一种方式就是用 @unchecked Sendable 告诉 Swift 这个类已经遵循 Sendable 了, 绕过 Swift 对这个类 Sendable 的检查.

举个🌰:

final class ThreadSafeCache: @unchecked Sendable {private var cache: [String: Sendable] = [:]// 虽然有可变状态,但通过队列保证了线程安全private let queue = DispatchQueue(label: "cache", attributes: .concurrent)func get(_ key: String) -> Sendable? {queue.sync {cache[key]}}func set(_ key: String, value: Sendable) {queue.async(flags: .barrier) {self.cache[key] = value}}
}

但为了防止 @unchecked Sendable 被人滥用, Swift6 提出了 Synchronization 模块的 Mutex 类型. 这个类型可以实现真正的 Sendable, 无需再依赖 @uncheck.

2.2.2. Synchronization 模块的 Mutex

Synchronization 模块的 Mutex 类型可以实现真正的 Sendable, 无需再依赖 @uncheck.

import Synchronizationfinal class ThreadSafeCache: Sendable {private let cache = Mutex<[String: Sendable]>([:])func get(_ key: String) -> Sendable? {cache.withLock {$0[key]}}func set(_ key: String, value: Sendable) {cache.withLock {$0[key] = value}}
}

四、@sendable 声明闭包线程安全

1、@sendable 用处

@sendable 就是用来修饰闭包的, 来确保闭包捕获的变量都遵循 sendable 协议; 以此来确保被闭包捕获的变量都是线程安全的.

2、如何使用 @sendable

func runLater(_ function: @escaping @Sendable () -> Void) -> Void {DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}

五、语法糖 —— 防止 Swift 的隐式推理是否遵循协议

你可以通过下面两种方式来阻止 Swift 隐式推理是否遵循协议

// ------------------------ 方式 1: @available(*, unavailable) 修饰 ---------------------------
@available(*, unavailable)
extension FileDescriptor: Sendable { }// ------------------------ 方式 2: 协议名前加 ‘~’ ---------------------------
struct FileDescriptor: ~Sendable {let rawValue: Int
}

本文参考:
Swift sendable typeshttps://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Sendable-TypesiOS data raceshttps://developer.apple.com/documentation/xcode/data-races

Swift6 sendable typeshttps://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety#Sendable-Types

hacking with swifthttps://www.hackingwithswift.com/swift/5.5/sendablehttps://www.avanderlee.comhttps://www.avanderlee.com/swift/sendable-protocol-closures/

By 东坡肘子:https://fatbobman.com/zh/posts/sendable-sending-nonsending/#unchecked-sendable

http://www.dtcms.com/a/605217.html

相关文章:

  • RK3568平台开发系列讲解:RK VOP 显示控制器
  • 《R for Data Science (2e)》免费中文翻译 (第12章) --- Logical vectors(2)
  • Python同步vs异步性能对比实验-2
  • 深入理解C语言中的static和extern关键字
  • 做期货应关注什么网站双语网站建设网站
  • Aspose.Cells for java 在将xlsx 转化为pdf 时有渲染问题
  • 如何读懂英文科技文献中的公式:从畏难到掌握的系统方法
  • Ansible,Playbook的简单应用
  • C++ 面试高频考点 链表 迭代 递归 力扣 25. K 个一组翻转链表 每日一题 题解
  • Unity Shader Graph 3D 实例 - 一个简单的高亮能量Buff
  • [Column] 构建十亿/s级DB | 索引DBRTDB | Kafka 为中心 | Rust 构建引擎
  • 项目八:Agent与自动化工作流(跨境电商AI运营助手Agent系统)
  • 百日挑战——单词篇(第二十一天)
  • Modbus协议详细介绍
  • 无人机遥控器频段与通道数详解
  • 网站开发兼职网站php做网站安装
  • 网站提示 “不安全”?免费 SSL 证书一键解决
  • wordpress链接域名南宁seo团队费用是多少
  • 如何实现Plaxis复杂工况自动化?Python在渗流、动力与参数化分析中的应用
  • 关于Unity 轴心点 Pivot、锚点 Anchor和控制轴
  • 整合Spring Cloud Alibaba与Gateway实现跨域的解决方案
  • 沙猫算法详细原理流程,公式,沙猫算法优化神经网络
  • Centos7 搭建hadoop2.7.2、hbase伪分布式集群
  • NetCoreKevin-基于NET8搭建DDD-微服务-现代化Saas企业级WebAPI前后端分离架构
  • 服装店网站建设思路thinkphp企业网站模板下载
  • 销售网站排名广州seo全网营销
  • QF-Lib:用一个库搞定Python量化回测和策略开发
  • 江西中赣建设有限公司网站绵阳市建设银行网站
  • 更新原生小程序封装(新增缓存订阅)完美解决
  • 医疗小程序07设置默认卡