Swift 中的 Actors 使用以及如何防止数据竞争

news2025/1/9 0:12:31

文章目录

    • 前言
    • Actors 的基本原理
      • Actor 是引用类型,但与类相比仍然有所不同
    • 为什么会出现数据竞争
    • 如何防止数据竞争
    • 使用 async/await 访问数据
    • 防止不必要的暂停
    • 非隔离(nonisolated)访问
    • 为什么在使用 Actors 时仍会出现数据竞争?
    • 总结

前言

Actors 是 Swift 5.5 引入的一种并发编程模型,用于管理共享数据并提供数据访问的安全性。Actors 使用异步消息传递来保护数据,防止数据竞争和其他并发问题。在这篇回答中,我将解释 Actors 的基本原理,并提供一些示例代码来说明其用法和如何防止数据竞争。

Swift 中的 Actors 旨在完全解决数据竞争问题,但重要的是要明白,很可能还是会遇到数据竞争。本文将介绍 Actors 是如何工作的,以及你如何在你的项目中使用它们。

Actors 的基本原理

Actors 是并发编程中的一种实体,它封装了一组相关的数据和操作,并且只能通过异步消息进行访问。每个 Actor 在任意时刻只能执行一个任务,从而避免了数据竞争。其他代码通过向 Actor 发送异步消息来请求数据或执行操作,Actor 在收到消息后逐个处理它们。

Actors 使用 async 和 await 关键字定义异步函数和等待异步结果,从而支持并发操作。每个 Actor 中的数据都是私有的,只能通过 Actor 提供的方法进行修改和访问,以保证数据的一致性和安全性。

下面是一个简单的示例,展示了如何定义和使用 Actor:

actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() async -> Int {
        return value
    }
}

// 创建 Counter Actor 实例
let counter = Counter()

// 在异步任务中调用 Actor 方法
Task {
    await counter.increment()
    let result = await counter.getValue()
    print("Counter value: \(result)")
}

在上面的代码中,Counter 是一个简单的 Actor,包含一个私有的 value 变量和两个方法 increment 和 getValue。increment 方法用于增加 value 的值,getValue 方法用于获取当前的 value 值。

在异步任务中,我们创建了一个 Counter 的实例 counter,并通过 await 关键字调用了 increment 和 getValue 方法。注意,在调用 Actor 的方法时,我们使用了 await 关键字来等待异步操作完成,并确保在访问和修改 Actor 数据时的安全性。

Actor 是引用类型,但与类相比仍然有所不同

Actor 是引用类型,简而言之,这意味着副本引用的是同一块数据。因此,修改副本也会修改原始实例,因为它们指向同一个共享实例。你可以在我的文章Swift 中的 Struct 与 class 的区别中了解更多这方面的信息。

然而,与类相比,Actor 有一个重要的区别:他们不支持继承。

Swift中的Actor几乎和类一样,但不支持继承。

Swift 中的 Actor 几乎和类一样,但不支持继承。

不支持继承意味着不需要像便利初始化器和必要初始化器、重写、类成员或 openfinal 语句等功能。

然而,最大的区别是由 Actor 的主要职责决定的,即隔离对数据的访问。

为什么会出现数据竞争

数据竞争是多线程/并发编程中常见的问题,它发生在多个线程同时访问共享数据,并且至少其中一个线程对该数据进行了写操作。当多个线程同时读写共享数据时,数据的最终结果可能会产生不确定性,导致程序出现错误的行为。

数据竞争发生的原因主要有以下几个方面:

1、竞态条件(Race Condition):当多个线程在没有适当同步的情况下并发地访问共享数据时,它们的执行顺序是不确定的。这可能导致数据的交错读写,从而导致数据的最终结果出现错误。

2、缺乏同步:如果多个线程在没有适当的同步机制的情况下访问共享数据,就会产生数据竞争。例如,多个线程同时对同一个变量进行写操作,而没有使用互斥锁或其他同步机制来保证原子性。

3、共享资源的修改:当多个线程同时对共享资源进行写操作时,就会产生数据竞争。如果没有适当的同步机制来保护共享资源的一致性,就会导致数据竞争。

4、可见性问题:多个线程可能具有各自的本地缓存或寄存器,这导致它们对共享数据的可见性存在延迟。当一个线程修改了共享数据,其他线程可能无法立即看到这个修改,从而导致数据竞争。

数据竞争可能导致程序出现各种问题,包括不确定的结果、崩溃、死锁等。为了避免数据竞争,需要采取适当的并发控制措施,例如使用锁、互斥量、信号量等同步机制来保护共享数据的访问,或者使用并发安全的数据结构来代替共享数据。

在 Swift 中,引入了一些并发编程的机制,如 async/await 和 Actor,可以帮助开发者更容易地处理并发问题和避免数据竞争。但仍需要开发者在编写并发代码时注意使用正确的同步机制和遵循最佳实践,以确保数据的安全和正确性。

如何防止数据竞争

Actors 通过限制同一时间只有一个任务可以访问 Actor 中的数据来防止数据竞争。这种限制确保了数据的一致性和线程安全性,而无需显式使用锁或其他同步机制。

Actors 还提供了数据的原子性访问和修改操作。在 Actor 内部,数据可以在异步环境中自由地修改,而不需要额外的同步操作。同时,Actors 会保证 Actor 的内部状态在处理完一个消息之前不会被其他任务访问,从而避免了并发问题。

在上面的示例中,我们可以看到在异步任务中通过 await 关键字调用 Counter 的方法。这样做可以确保在不同的任务中对 Counter 的访问是串行的,从而避免了数据竞争和其他并发问题。

Actors 是 Swift 中用于并发编程的一种模型,它通过异步消息传递来保护共享数据,并防止数据竞争。下面是一些阐述 Actors 的使用和防止数据竞争的关键要点:

1、定义 Actor:使用 actor 关键字来定义一个 Actor 类,将要保护的数据和相关操作封装在其中。例如:

actor MyActor {
    private var sharedData: Int = 0

    func performTask() {
        // 对共享数据进行操作
    }
}

2、异步访问:通过 async 和 await 关键字来标记异步函数和等待异步结果。只有通过异步函数或方法访问 Actor 中的数据才是安全的。例如:

actor MyActor {
    private var sharedData: Int = 0

    func performTask() async {
        sharedData += 1
        await someAsyncOperation()
        let result = await anotherAsyncOperation()
        // 对共享数据进行操作
    }
}

3、 发送异步消息:通过在 Actor 实例上使用 await 关键字来发送异步消息,并调用 Actor 中的方法。这样做可以确保对 Actor 的访问是串行的,从而避免了数据竞争。例如:

let myActor = MyActor()

Task {
    await myActor.performTask()
}

4、数据保护:由于 Actors 限制了同一时间只能执行一个任务,因此可以保证对共享数据的访问是串行的,从而避免了数据竞争。Actors 还提供了内部状态的保护,确保在处理一个消息之前不会被其他任务访问。

使用 async/await 访问数据

在 Swift 中,使用 async/await 关键字来进行异步访问数据是一种安全且方便的方式,特别适用于访问 Actor 中的共享数据。下面是一些示例代码来说明如何使用 async/await 访问数据:

actor MyActor {
    private var sharedData: Int = 0

    func readData() async -> Int {
        return sharedData
    }

    func writeData(value: Int) async {
        sharedData = value
    }
}

// 创建 MyActor 实例
let myActor = MyActor()

// 异步读取共享数据
Task {
    let data = await myActor.readData()
    print("Shared data: \(data)")
}

// 异步写入共享数据
Task {
    await myActor.writeData(value: 10)
    print("Data written successfully")
}

在上面的示例中,readData 方法和 writeData 方法被标记为 async,表示它们是异步的。通过 await 关键字,我们可以在异步任务中等待数据的读取和写入操作完成。

使用 await 关键字调用 readData 方法时,任务会等待直到共享数据的读取操作完成,并将结果返回。类似地,使用 await 关键字调用 writeData 方法时,任务会等待直到共享数据的写入操作完成。

需要注意的是,在使用 async/await 访问数据时,要确保访问的方法或属性是异步的。对于 Actor 中的方法,可以在其声明前加上 async 关键字,表示它们是异步的。对于属性,可以将其声明为异步的计算属性。

防止不必要的暂停

在上面的例子中,我们正在访问我们 Actor 的两个不同部分。首先,我们更新吃食的鸡的数量,然后我们执行另一个异步任务,打印出吃食的鸡的数量。每个 await 都会导致你的代码暂停,以等待访问。在这种情况下,有两个暂停是有意义的,因为两部分其实没有什么共同点。然而,你需要考虑到可能有另一个线程在等待调用 chickenStartsEating,这可能会导致在我们打印出结果的时候有两只吃食的鸡。

为了更好地理解这个概念,让我们来看看这样的情况:你想把操作合并到一个方法中,以防止额外的暂停。例如,设想在我们的 actor 中有一个通知方法,通知观察者有一只新的鸡开始吃东西:

extension ChickenFeeder {
    func notifyObservers() {
        NotificationCenter.default.post(name: NSNotification.Name("chicken.started.eating"), object: numberOfEatingChickens)
    }
} 

我们可以通过使用 await 两次来使用此代码:

let feeder = ChickenFeeder()
await feeder.chickenStartsEating()
await feeder.notifyObservers() 

然而,这可能会导致两个暂停点,每个 await 都有一个。相反,我们可以通过从 chickenStartsEating 中调用 notifyObservers 方法来优化这段代码:

func chickenStartsEating() {
    numberOfEatingChickens += 1
    notifyObservers()
} 

由于我们已经在 Actor 内有了同步的访问,我们不需要另一个等待。这些都是需要考虑的重要改进,因为它们可能会对性能产生影响。

非隔离(nonisolated)访问

在 Swift 中,非隔离(nonisolated)访问是指在一个异步函数内部访问类、结构体或枚举的非隔离成员。异步函数默认情况下是隔离的,这意味着在异步函数内部只能访问该类型的隔离成员。但有时候我们需要在异步函数中访问非隔离成员,这时就可以使用非隔离访问。

为了进行非隔离访问,需要使用 nonisolated 关键字来修饰访问权限。例如,假设有一个类 MyClass,其中有一个非隔离成员属性 value:

class MyClass {
    nonisolated var value: Int = 0
}

现在,我们可以在异步函数内部访问 MyClass 的 value 属性:

func asyncFunction() async {
    let instance = MyClass()
    await doSomething(with: instance.value) // 非隔离访问
}

需要注意以下几点:

1、非隔离访问只能在异步函数内部进行。在同步环境下或其他异步函数内部,仍然需要使用隔离访问。

2、非隔离访问是一种权限放宽的操作,因此需要谨慎使用。确保在异步函数中对非隔离成员的访问是安全的,并且不会引入数据竞争或其他问题。

3、非隔离访问只适用于可变性为非 nonmutating 的成员,即对可修改的成员进行访问。对于可读的非隔离成员,可以直接使用隔离访问。

为什么在使用 Actors 时仍会出现数据竞争?

当在你的代码中持续使用 Actors 时,你肯定会降低遇到数据竞争的风险。创建同步访问可以防止与数据竞争有关的奇怪崩溃。然而,你显然需要持续地使用它们来防止你的应用程序中出现数据竞争。

在你的代码中仍然可能出现竞争条件,但可能不再导致异常。认识到这一点很重要,因为Actors 毕竟被宣扬为可以解决一切问题的工具。例如,想象一下两个线程使用 await正确地访问我们的 Actor 的数据:

queueOne.async {
    await feeder.chickenStartsEating()
}
queueTwo.async {
    print(await feeder.numberOfEatingChickens)
} 

这里的竞争条件定义为:“哪个线程将首先开始隔离访问?”。所以基本上有两种结果:

  • 队列一在先,增加吃食的鸡的数量。队列二将打印:1
  • 队列二在先,打印出吃食的鸡的数量,该数量仍为:0

这里的不同之处在于我们在修改数据时不再访问数据。如果没有同步访问,在某些情况下这可能会导致无法预料的行为。

总结

使用 async/await 关键字来访问数据是 Swift 中一种安全且方便的方式。在访问 Actor 中的共享数据时,可以使用 async/await 关键字来标记异步方法,并通过 await 关键字等待读取和写入操作的完成。这样可以确保数据的访问是线程安全的,并且能够充分利用 Swift 提供的并发编程能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/582778.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FFMPEG常用命令 音视频合并

目录 一、音频合并 1.获取音频时长 2.合并两段音频 3.合并音频插入空白 二、视频加背景图 三、音视频合成 1.保留视频声音 2.不保留视频声音 四、合并视频 本文将用几个实例,介绍ffmpeg命令的综合使用,主要涉及音频处理、视频处理和音视频合成。…

如何搭建自己的CentOS系统

CentOS是一个完全免费的操作系统,这对于开发人员来说非常有吸引力。他们可以使用CentOS来开发和测试应用程序,而不需要支付任何费用。那么作为程序员如果搭建自己的CentOS服务器呢? 搭建自己的CentOS系统需要以下步骤: 1、下载Ce…

借助KafkaTool在海量Kafka数据快速精准定位/查询目标数据

如何在海量的Kafka数据定位/查询精准到秒级的数据? 解决方案:Kafka命令 KafkaTool 1. 使用Kafka命令定位Offset sh ./kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list $ip:$port -topic ${topic.name} -time $timestamp参数说明&#…

程序员大战黄牛党,破解演唱会门票秒光之困

反黄牛,为何是场持久战? 撰文 | 林秋艺 编辑 | 龚 正 这个五月,似乎都被五月天霸屏了。从5月9日的30万张五月天演唱会门票被5秒扫光;到粉丝群起反抗,喊出“宁可鸟巢门口站,也不能让黄牛赚”,…

基于实体类导出excel模板

很多需求是导入excel的功能&#xff0c;但是导入的时候需要excel模板。如果很多模板的&#xff0c;就需要一个通过工具类来实现了&#xff0c;根据配置好一个实体类来实现模板的导出。 <dependency><groupId>org.jodd</groupId><artifactId>jodd-core&…

创建型设计模式03-原型模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 &#x1f3e1;账号&#xff1a;CSDN 、个人博客 、Github &#x1f38a;公众号&#xff1a;猫十二懿 原型模式 1、原型模式介绍 原型模式是一种创建型设计模式&#xff0c;它允许通过复制现有对象来生成新对象&#xf…

【大数据处理与可视化】五、数据聚合与分组运算

【大数据处理与可视化】五、数据聚合与分组运算 实验目的实验内容实验步骤一、案例——运动员信息的分组与聚合1、统计男篮、女篮运动员的平均年龄、身高、体重2、统计男篮运动员的平均年龄、身高、体重的极差值3、统计男篮运动员的体质指数 实验小结 实验目的 能够熟练运用gr…

NISEDIT如何发布,Qt如何发布文章?难道还有人不会(超详细教学,跟着走,不会你怪我)

一、自动发布 直接运行即可&#xff0c;不过多阐述。 二、手动发布 文件清单&#xff1a; ExamSys.exe account.txt、exam.txt Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll libstdc-6.dll、libwinpthread-1.dll、libgcc_s_sjlj-1.dll、libgcc_s_dw2-1.dll 注意&#xff1a…

Vue项目中vuex的安装和使用

Vue项目中vuex的安装和使用 1. 安装vuex2. 导入vuex包并创建store仓库3. 在main.js中导入store实例4. 检测vuex是否能正常使用 1. 安装vuex npm install vuex --save2. 导入vuex包并创建store仓库 在项目文件目录中&#xff0c;在src目录下创建一个名为store的文件夹&#xf…

图表控件LightningChart JS使用指南 - 如何创建仪表图

LightningChart JS是性能最高的JavaScript图表库&#xff0c;专注于实时数据可视化。是Web上性能最高的图表库具有出色的执行性能 - 使用高数据速率同时监控数十个数据源。 GPU加速和WebGL渲染确保您的设备的图形处理器得到有效利用&#xff0c;从而实现高刷新率和流畅的动画。…

【大数据处理与可视化】六、数据可视化

【大数据处理与可视化】六、数据可视化 实验目的实验内容实验步骤一、案例——画图分析某年旅游景点数据1、河北省总面积和游客量位居前三的景点2、河北省旅游量的占比哪个最多&#xff0c;哪个最少。 实验小结 实验目的 1.能够详述常见图表的类型和特点。 2.能够熟练运用Matp…

ELFK日志分析系统并使用Filter对日志数据进行处理

目录 一、 FilebeatELK 部署Filebeat 节点上操作 二、Filtergrok 正则捕获插件内置正则表达式调用自定义表达式调用 mutate 数据修改插件multiline 多行合并插件date 时间处理插件 一、 FilebeatELK 部署 Node1节点&#xff08;2C/4G&#xff09;&#xff1a;node1/192.168.15…

【数据结构】虽然很难很抽象,但是你还是得努力弄懂的数据结构——数组,你常用但是你懂它吗

数组(Array) 数组是实现顺序存储结构的基础,数组(Array)存储具有相同数据类型的元素集合.一维数组占用一块内存空间,数组的存储单元个数称为数组容量,也称为数组长度. 每个存储单元的地址是连续的,即每个元素连续存储,计算第i个元素地址所需时间是一个常量,时间复杂度是O(1),…

【论文解读|GL-Cache 】基于组级学习的缓存替换算法

论文原文&#xff1a; GL-Cache: Group-level learning for efficient and high-performance caching | FAST 23 源码 地址&#xff1a; GitHub - Thesys-lab/fast23-GLCache: Repository for FAST23 paper GL-Cache: Group-level Learning for Efficient and High-Performance…

基于Python+百度语音的智能语音ChatGPT聊天机器人(机器学习+深度学习+语义识别)含全部工程源码 适合个人二次开发

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Pycharm 环境ChatterBot 环境 模块实现1. 模型构建2. 服务器端3. 客户端4. 语音录入5. 接口调用6.模型训练及保存 系统测试1. 模型效果2. 模型应用 参考资料其它资料下载 前言 本项目基于机器学习和语义识别技术…

Qt翻金币小游戏详细教程(内涵所有源码、图片资源)

一、项目简介 翻金币项目是一款经典的益智类游戏&#xff0c;我们需要将金币都翻成同色&#xff0c;才视为胜利。首先&#xff0c;开始界面如下&#xff1a; 点击start按钮&#xff0c;进入下层界面&#xff0c;选择关卡&#xff1a; 在这里我们设立了20个关卡供玩家选择&…

IDEA使用技巧

1. 安装教程 1.1 安装过程 1.2 安装后的软件目录结构 目录结构&#xff1a; bin&#xff1a;容器&#xff0c;执行文件和启动参数等 这里以我的电脑系统(64 位 windows7&#xff0c; 16G 内存)为例&#xff0c;说明一下如何调整 VM 配置文件&#xff1a; 1、大家根据电脑系统…

【(Ubuntu22.04 Jammy)安装ROS 2 Iron Irwini】

ROS2 IronIrwini的Debian软件包目前可用于Ubuntu22.04 Jammy 1、安装ROS2 IronIrwini前准备 需要先安装好Ubuntu22.04 Jammy的前提下开始安装ROS2 1.1 Set locale 请确保Set locale支持UTF-8 locale # check for UTF-8sudo apt update && sudo apt install locale…

Rhapsody新手提示(1)如何在安装之后更换界面语言

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> 安装之后&#xff0c;如果还想更换界面语言&#xff0c;Windows平台操作如下&#xff1a; &#xff08;1&#xff09;在C:\ProgramData\IBM\Rhapsody\9.0.1x64文件夹下找到…

2023 年最新、最全、最实用的 Java 岗面试真题,已收录 GitHub

Java 面试 Java 作为编程语言中的 NO.1,选择入行做 IT 做编程开发的人&#xff0c;基本都把它作为首选语言,进大厂拿高薪也是大多数小伙伴们的梦想。以前 Java 岗位人才的空缺&#xff0c;而需求量又大&#xff0c;所以这种人才供不应求的现状&#xff0c;就是 Java 工程师的薪…