塞雷赛
返回对基础数据的可变引用。由于这个调用可变地借用Mutex,所以不需要发生实际的锁定-可变借用静态地保证不存在锁。
所以问题是不需要锁定。当然,如果互斥体(in)没有直接从任何不安全的块中使用,那么任何线程都不可能持有对它的引用,因此不能使用它。这也意味着,为了编译get_mut的调用,如果任何线程正在使用它,则该线程必须已经完成其执行。任何新线程都可以访问受互斥锁保护的数据,因为它将从内存中读取,就像获取语义一样,因此通过get_mut对数据所做的任何更改对任何新线程都是可见的。
然而,如果一个互斥体是从一个不安全的块中使用的,那么有可能某个线程(像我的例子中的ffi)仍然在互斥体上有一个指针,并且可以持有锁并使用数据,或者只是完成数据的变异并解锁互斥体。在前一种情况下,它显然是UB,在后一种情况下,它是一个竞争条件,这也是UB,因为调用get_mut的线程可能看不到受保护数据的最新状态,该状态刚刚由另一个线程更新,该线程通过指针访问互斥体。为了使最新的更改在调用get_mut的线程上可见,应该保持获取/释放语义,但get_mut不调用任何获取操作。
get_mut是一个有用的函数,但我不相信它是线程安全的,因此应该是不安全的。
3条答案
按热度按时间j1dl9f461#
Rust中可变引用的主要属性是它们是unique。也就是说,当代码作为一个整体是合理的,它保证了 * 唯一 * 的方式来访问可变引用引用的值是通过确切的引用。在安全代码中,这是自动执行的。在不安全的代码中,包括FFI,这是不变量 you,作为编写
unsafe
的人,应该维护。特别是,当你有对
Mutex
的可变引用时,让任何其他地方访问同一个互斥体或它后面的数据都是显式和立即的UB。特别是,让其他人锁定它本质上是UB,因为根据定义,解锁将访问互斥体(以存储“未锁定”位)。因此,如果某些
unsafe
代码导致get_mut
是非线程安全的,那么这种不安全的代码本身就是不可靠的。wd2eg0qa2#
如果您只使用安全代码,那么可变引用的存在就意味着您在编译时有一个happens-before关系,因此是同步的。
如果你使用不安全的代码来访问锁,那么你有责任确保存在happens-before关系。如果不这样做,即使没有实际更改数据,代码也是不可靠的,因为您正在创建两个重叠的可变引用,而没有同步,因此是UB。因此,您负责创建正确的同步。如果你做了,一切都很好。如果你不这样做,* 你的 * 不安全代码是不健全的,UB来自 * 你的 * 不安全代码。std的
get_mut()
在这里不起作用,因此是合理的。mqxuamgl3#
我的答案是,是的,当然它应该是不安全的。在ARM上运行这个程序(x64可以很好地工作,因为内存很强)。有两个不安全的块,它们的所有安全条件都得到满足,因此这是一个合理的(从借用检查器的Angular 来看)代码,但它不是线程安全的。将两个get_mut都替换到锁上,就像预期的那样解决了问题。
PS:我决定在这里做一个额外的解释,因为评论并不真正起作用。让我们看看
Arc::drop
和他们的评论所以现在,我对
std::sync::Mutex
的整个不合理性有了更强烈的看法。只有互斥锁的锁族方法是可以的,get_mut
和drop
不是线程安全的。记住,Mutex抽象的全部意义在于通过公共安全API提供线程安全访问->确保任何公共安全方法在任何安全场景中彼此之间都有happens-before关系。回到
Arc::drop
,它已经有一个划痕来解决Mutex::drop
中的bug。如果Mutex::drop
能确保与所有其他Mutex
公共安全API函数之间的happens-before关系,那么Arc
可以对每种类型使用简单的宽松排序。1.假设
Arc<T>
中的T
没有内部可变性,那么rust语言保证在计数器上使用宽松顺序总是安全的,因为对受保护数据的唯一访问是加载。1.让我们假设
Arc<T>
中的T
暴露了内部可变性,那么Arc
应该确保happens-before关系(c++std::shared_ptr
所做的并被rust模仿)或者(这更符合逻辑)具有内部可变性的类型T必须确保其公共安全API中的happens-before关系。现在,由于
Arc
和Mutex
设计选择之间错误的责任分配,所有其他类似于Arc
的代码都必须模仿这种糟糕的设计,并确保happens-before关系。另一个参数,在一个普通的程序中,
Arc::drop
调用与Mutex::drop
调用的比例是多少?我打赌比1少得多。因此,在Mutex
中确保happens-before关系比在其他任何地方都更理想,这是Mutex
抽象在像rust这样的安全语言中应该做的事情。同样,拥有&mut ref并不假设在已删除的数据上有任何happens-before关系,它只保证没有任何其他ref,在我的例子中,情况就是这样。还记得
Mutex
的内部结构吗?它包含UnsafeCell
和sys::Mutex
,在我的例子中,sys::Mutex
上的任何操作都是线程安全的,并且它由sys::Mutex
实现保证,但访问UnsafeCell
不是,需要happens-before关系,Mutex
是什么?换句话说,如果你将
Mutex
抽象分解为一对sys::Mutex
和UnsafeCell
,并在这个装箱的对上传递一个指针,那么在将指针转换回引用之后,你可以在所有场景中安全地使用sys::Mutex
部分,但是你不能在不确保happens-before关系的情况下使用UnsafeCell
。PPS:基本上,所有的论点,从那些否决这个答案的人,可以缩小到一个简单的句子:
在rust中,在任何安全代码中使用
std::sync::atomic::Ordering::Relaxed
和std::sync::AtomicPtr
都是非法的。我可以离开并接受这个观点,当这将被编码在生 rust 类型系统。