【问题标题】:Is it safe to move a mutex in Swift?在 Swift 中移动互斥锁是否安全?
【发布时间】:2021-01-10 22:54:50
【问题描述】:

我正在对互斥锁进行一些研究,并遇到了以下 Swift 代码:

class Lock {

    private var mutex: pthread_mutex_t = {
        var mutex = pthread_mutex_t()
        pthread_mutex_init(&mutex, nil)
        return mutex
    }()

    func someFunc() {
        pthread_mutex_lock(&mutex)
        defer { pthread_mutex_unlock(&mutex) }
        ...
    }

}

代码在闭包中定义并初始化一个 pthread_mutex_t,然后将返回的值分配给类属性。然后它会在所示的几个函数中锁定和解锁。

由于还应该调用 pthread_mutex_destroy,这意味着在互斥体中发生了某种分配,它可能引用或不引用原始值的地址。

实际上,互斥体在一个地方初始化并存储在另一个地方。

问题是这样做是否安全或正确?

如果互斥量初始化器需要参数怎么办?

    private var mutex: pthread_mutex_t = {
        var recursiveMutex = pthread_mutex_t()
        var recursiveMutexAttr = pthread_mutexattr_t()
        pthread_mutexattr_init(&recursiveMutexAttr)
        pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE)
        pthread_mutex_init(&recursiveMutex, &recursiveMutexAttr)
        return recursiveMutex
     }()

我觉得后者肯定是不正确的,因为当闭包崩溃时,其地址被传递到互斥锁中的属性存储将消失。

【问题讨论】:

    标签: swift concurrency pthreads


    【解决方案1】:

    不是,这段代码坏了。

    要工作,pthread_mutex_t 需要在类实例中就地初始化,并且从不复制出来。该类需要公开锁定/解锁方法,该方法对实例变量进行就地操作。

    值类型

    请注意,pthread_mutex_tpthread_rwlock_tos_unfair_lock 是值类型,而不是引用类型。这意味着如果你在它们上使用 =,你就会复制一份。这很重要,因为这些类型无法复制!如果您复制其中一种 pthread 类型,则该副本将无法使用,并且在您尝试使用时可能会崩溃。

    ——By Mike Ash, Friday Q&A 2017-10-27: Locks, Thread Safety, and Swift: 2017 Edition

    查看https://cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html

    这个示例在 Swift 中使用 pthread_mutex_thttps://github.com/mattgallagher/CwlUtils/blob/0bfc4587d01cfc796b6c7e118fc631333dd8ab33/Sources/CwlUtils/CwlMutex.swift#L60-L105

    【讨论】:

    • 我真的很好奇这不违反 swift 是强类型的概念。导致形成有效无用对象的简单操作(复制)似乎与 C 等语言支持的类型错误没有本质区别。
    • 类型安全只是程序安全的一个(尽管非常有用且常见)维度。有很多方法可以给自己打枪,尤其是在涉及穿线的情况下。如果有什么安慰的话,只能移动数据类型正在开发中,这是它们可能的应用之一。虽然真的,IMO,苹果应该只是在pthread_mutex_t 周围提供了正确/安全的包装器,并将它完全隐藏在 Swift 中(IIRC,NSLock 正是这个)。
    • 不会移动才使副本中的原件无效吗?就像我有一个包含互斥锁的对象 Q,然后我制作了一个副本 P;现在 P 的互斥锁控制 Q,而 Q 是随机的?也许我应该考虑过去三年 swift 迁移到的任何版本。
    • "不会移动才使副本中的原件无效吗?"据我了解,是的。不同之处在于,如果这是语言支持的功能,编译器可以强制以任何方式无法访问旧位置。
    • 例如看看UnsafeMutableBufferPointer<T>.withMemoryRebound(to:_:)。它在某种意义上做了一个动作,但没有编译器强制执行:“因为在执行 body 闭包时,这个缓冲区的内存不再绑定到它的 Element 类型,所以不要使用 body 内的原始缓冲区访问内存。”您可以仍然访问原始缓冲区。它是 UB,但在编译器知道只移动类型之前,没有什么能阻止你。
    猜你喜欢
    • 1970-01-01
    • 2017-10-23
    • 2012-06-05
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多