建设一个购物网站的费用,网站建设江苏百拓,怎么做网站解析,莱芜正规的网站建设文章目录 前言Actors 的基本原理Actor 是引用类型#xff0c;但与类相比仍然有所不同 为什么会出现数据竞争如何防止数据竞争使用 async/await 访问数据防止不必要的暂停非隔离(nonisolated)访问为什么在使用 Actors 时仍会出现数据竞争#xff1f;总结 前言
Actors 是 Swif… 文章目录 前言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 0func 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 几乎和类一样但不支持继承。
不支持继承意味着不需要像便利初始化器和必要初始化器、重写、类成员或 open 和 final 语句等功能。
然而最大的区别是由 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 0func performTask() {// 对共享数据进行操作}
}2、异步访问通过 async 和 await 关键字来标记异步函数和等待异步结果。只有通过异步函数或方法访问 Actor 中的数据才是安全的。例如
actor MyActor {private var sharedData: Int 0func performTask() async {sharedData 1await 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 0func 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 1notifyObservers()
} 由于我们已经在 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 提供的并发编程能力。