在 Swift async/await 中,我可以使用 Lock 还是 Semaphore

2024-05-12

这不是问题,这是一个想寻求帮助以及专业指导的问题。

根据文档,Sendable 类型可以在 Swift Concurrency 中安全地传递。在旧项目中并非所有类型都是可发送的,并且可能使用Cocoa类型,但它们是线程安全的,例如:

class DemoPerson: NSObject {

  ...

  private var _phoneNumbers: NSMutableArray = NSMutableArray()
  private var lock = NSLock
  var phoneNumbers: NSArray {
     return _phoneNumbers.copy()
  }

  ...

  func removePhoneNumber(at index: Int) {
    lock.lock()
    ...
    lock.unlock()
  }


  func addPhoneNumber() {
    lock.lock()
    ...
    lock.unlock()
  }

  ...

另外,还有一些系统类也是线程安全的,比如UserDefaults。也许他们使用锁、信号量、原子等技术。

我认为那些是内部同步参考类型 https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md#internally-synchronized-reference-types,并且可以安全地并发使用。

在可发送文档中,Apple 说

要在没有任何编译器强制执行的情况下声明与 Sendable 的一致性,请编写@unchecked Sendable。您负责未经检查的可发送类型的正确性,例如,通过使用锁或队列保护对其状态的所有访问。如果未检查与 Sendable 的一致性,也会禁用一致性必须位于同一文件中的规则的执行。

但Apple也不建议在新并发中使用锁等同步机制,这可能会导致不可预测的问题。 我同意这一点,因为lock会导致当前线程挂起,async/await的特点是不阻塞当前线程。并且锁使得优先级处理变得非常困难。

那么我是否可以将它们标记为@unchecked,将它们视为普通的 Sendable 类型,而不会导致 async/await 产生不可预测的错误?


You ask:

在 Swift async/await 中,我可以使用 Lock 还是 Semaphore

简而言之,您可以,但要格外小心。您可以使用锁来同步对某些可变属性的快速访问。但不要使用锁来管理单独的 Swift 并发任务之间的依赖关系或任何可能会很慢的事情(例如,写入持久存储、执行网络请求等)。


在 WWDC 2021 视频中Swift 并发:幕后花絮 https://developer.apple.com/videos/play/wwdc2021/10254?time=1507,他们警告我们锁是允许的,但必须“谨慎”使用,即在非常具体的用例中:

基元如os_unfair_locks and NSLocks也很安全,但使用时需要小心。当用于紧密的、众所周知的临界区周围的数据同步时,在同步代码中使用锁是安全的。

因此,如果要对某些状态属性进行非常快速的线程安全同步,则可以使用锁(但要小心)。事实上,在 WWDC 2023 上超越结构化并发的基础知识 https://developer.apple.com/videos/play/wwdc2023/10170/?time=450,他们明确考虑了这种情况,使用final class那是Sendable:

虽然 Actor 对于保护封装状态非常有用,但我们希望修改和读取状态机上的各个属性,因此 Actor 并不是一个合适的工具。

此外,……我们可以使用调度队列或锁。

现在,在他们的示例中,他们将属性封装在ManagedAtomic https://github.com/apple/swift-atomics,所以他们不需要使用@unchecked,但是如果您在可变变量周围使用锁,则可以这样指定。

例如,这里有一个@unchecked Sendable使用锁的再现:

final class DemoPerson: @unchecked Sendable {
    private var _phoneNumbers: Array<PhoneNumber> = []
    private let lock = NSLock()
    
    var phoneNumbers: Array<PhoneNumber> {
        lock.withLock { _phoneNumbers }
    }
    
    @discardableResult
    func removePhoneNumber(at index: Int) -> PhoneNumber {
        lock.withLock { _phoneNumbers.remove(at: index) }
    }
    
    func addPhoneNumber(_ phoneNumber: PhoneNumber) {
        lock.withLock { _phoneNumbers.append(phoneNumber) }
    }
}

底线是,final class那是@unchecked Sendable可以使用锁。它有点脆弱,我们通常更喜欢提供编译时检查的 Swift 并发原语。但是,锁与 Swift 并发兼容(只要它仅限于“紧密的、众所周知的临界区”)。这就是提供的全部目的@unchecked Sendable模式:它允许我们按照自己的节奏优雅地过渡到 Swift 并发。但是,我们有责任确保代码的线程安全。


一些小观察:

  • 请注意,原始代码片段不是线程安全的,因为phoneNumber计算属性未同步;和@unchecked Sendable,我们失去了代码的编译时检查;我已经在上面解决了这个问题,但这很好地说明了为什么我们回避@unchecked模式,因为它很容易让一些边缘情况从我们身边溜走而未被发现;

  • 而不是手动lock and unlock,我们可能更喜欢withLock https://developer.apple.com/documentation/foundation/nslocking/4059821-withlock,保证加锁和解锁自动平衡;在这种情况下,逻辑非常琐碎,不那么重要,但如果您有更复杂的代码流程,手动锁定和解锁可能会变得更复杂且更脆弱;除了,withLock减少代码片段中的一些语法噪音,使锁定的意图更加明确,等等。

一些不相​​关的观察结果:

  • 我会让lock一个不可变的(let) 财产;

  • 我更喜欢使用值类型(例如,SwiftArray等)而不是NSMutableArray…另外,虽然您可以使用NSMutableArray,小心该数组中的对象......注意copy仅执行数组的浅表复制,您很容易导致与该数组中的对象的意外共享和不同步交互;和

  • 如果你需要NSObject接口,请随意这样做,但通常我们不会在 Swift 代码库中这样做;我在上面的例子中删除了它,但是如果你需要NSObject由于某些令人信服的原因而子类化,请随意这样做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Swift async/await 中,我可以使用 Lock 还是 Semaphore 的相关文章

随机推荐

  • FreeMarker - 获取当前 URL

    是否可以在 FTL 中获取当前页面的 URL 据我所知 freemarker 严格来说是一个模板引擎 它只是生成文本 并且无法知道该文本将出现在哪里 如果要包含 当前页面的 URL 则必须将该数据从主机 Java 代码传递到模板中 推荐 或
  • 在 angularjs 中的某些字段上设置条件焦点[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 您好 所有工程师同事 我看到了很多问题 并提出了关于将焦点设置在某些输入字段上的答案 但我没有找到任何可以满足我的要求的
  • Android 已弃用屏幕尺寸?

    嘿 我需要在我的应用程序中获取屏幕的宽度 该应用程序将在 2 1 及更高版本上运行 我已经将其设置为如下所示 该方法已被弃用 我可能应该使用 getSize 或其他方式 但问题是 这是否适用于 3 0 和 4 0 等 Android 版本
  • 如何确保使用 Microsoft Sync Framework 同步成功?

    我正在使用微软同步框架 https msdn microsoft com en us sync bb736753 aspx同步两个 Microsoft SQL Server 上的表 我创建了一个测试应用程序 它每秒在远程服务器上的表中生成一
  • 如何在 python 中生成音符或和弦?

    有人能给我指出一个在 python 2 7 中生成音符和和弦的好库吗 我查看了 PythonInfoWiki 但运气不佳 PyAudio 只是崩溃了 似乎没有其他东西可以生成音调 我不知道这是否有帮助 但这里有一些代码可以根据给定的频率和振
  • 为什么最后一个关闭的 MDI 子窗体没有被垃圾回收?

    我们的应用程序中存在内存泄漏问题 我已成功通过以下简单示例复制了其中一个问题 复制设置 1 创建以下辅助类 用于跟踪对象创建 销毁 public class TestObject public static int Count get se
  • Pandas:根据其他列值有条件地替换值

    我有一个数据框 df 如下所示 environment event time 2017 04 28 13 08 22 NaN add rd 2017 04 28 08 58 40 NaN add rd 2017 05 03 07 59 35
  • Kafka Streams 如何处理包含不完整数据的分区?

    Kafka Streams 引擎将一个分区映射到一个工作线程 即 Java 应用程序 以便该分区中的所有消息都由该工作线程处理 我有以下场景 并试图了解它是否仍然可行 我有一个主题 A 有 3 个分区 发送给它的消息由 Kafka 随机分区
  • “grep -q”的意义是什么

    我正在阅读 grep 手册页 并遇到了 q 选项 它告诉 grep 不向标准输出写入任何内容 如果发现任何匹配 即使检测到错误 也立即以零状态退出 我不明白为什么这可能是理想或有用的行为 在一个程序中 其原因似乎是从标准输入读取 处理 写入
  • Django ValueError:缺少静态文件清单条目,但清单似乎显示该条目

    在部署到 Heroku 的 Django 1 11 应用程序上 加载根 URL 时 我猜当 Django 到达时 static angular angular min js in the homepage html模板 我收到以下错误 Va
  • mat-table 中每行的 formGroup 数组

    我有一个 formGroup 数组 其中每个元素代表一个表单 接下来我有一个 mat 表 我想要做的是将每个 tr 即每一行 生成为不同的表单 以便表的第 i 行链接到第 i 个 formGroup 因此 在第 i 行内 每个 td 元素都
  • 更改 gnuplot 中 tics 之间的实际空间

    x 轴示例 Before 10 20 30 40 After 10 20 30 40 我已经搜索了一段时间 只找到了如何尽可能简单地缩放除抽动之间的大小之外的其他所有内容 我不想改变画布大小 终端大小 抽动大小 抽动数量等 我想拉伸 x 轴
  • Android 6 getAccountName() 缺少 android.permission.GET_ACCOUNTS

    在 Android 6 设备上运行时出现以下异常 java lang SecurityException Missing android permission GET ACCOUNTS 这看起来像是一个相当简单的例外 但对我来说并非如此 我
  • .NET Core Web API 密钥

    我正在开发一个应用程序 用户可以通过用户名和密码进行身份验证 我们提供一个 JWT 令牌 然后在服务器上进行验证 我想补充的一件事是能够拥有一个特殊的 API 密钥 guid 用户在与此应用程序集成时可以使用该密钥 而不是使用用户名和密码
  • 当绑定值为 null 时出现 WPF 日期选择器验证错误

    我有一个 WPF 应用程序 其中使用绑定到实体框架 带有 SQL Server 实体的日期字段的日期选择器 我将其绑定如下
  • 如何在 C# 中将 cookie 过期设置为“会话”?

    不言自明 在 PHP 中 解决方案是将 cookie 过期设置为 0 我不确定 C 因为它需要 DateTime 值 的文档Cookie 过期 http msdn microsoft com en us library system net
  • 在一条语句中对多个变量进行相同的赋值

    有没有一种方法可以为不同的变量分配相同的值 而无需在单个语句中构造数组 例如 如果我有变量a b c d and e 我可以分配类似的东西吗 a b c d e 10 0 我知道我可以用一行来做 a 10 0 b 10 0 c 10 0 d
  • 可以禁用“应用程序错误”对话框吗?

    我使用 Hudson 作为持续集成服务器来测试 C C 代码 不幸的是 我在某个地方有一个错误导致内存损坏 因此在某些 Windows 计算机上我有时会收到一个 应用程序错误 对话框 解释一条指令引用了无法读取的内存 弹出此对话框并基本上挂
  • Java switch 语句:需要常量表达式,但它是常量

    因此 我正在研究这个具有一些静态常量的类 public abstract class Foo public static final int BAR public static final int BAZ public static fin
  • 在 Swift async/await 中,我可以使用 Lock 还是 Semaphore

    这不是问题 这是一个想寻求帮助以及专业指导的问题 根据文档 Sendable 类型可以在 Swift Concurrency 中安全地传递 在旧项目中并非所有类型都是可发送的 并且可能使用Cocoa类型 但它们是线程安全的 例如 class