首先,重要的是要意识到大多数结构(或枚举)都是Send:
- 任何不包含任何引用的结构都可以是
Send + 'static
- 任何包含下界生命周期为
'a 的引用的结构都可以是Send + 'a
因此,您通常会期望任何 Sync struct 也是 Send,因为 Send 是一个非常容易到达的栏(与更难的 Sync 相比,它需要来自多个线程的安全并发修改)。
但是,没有什么能阻止类型的创建者专门将其标记为不是Send。例如,让我们恢复条件!
在 Lisp 中,条件的概念是您为给定条件设置一个处理程序(例如:FileNotFound),然后当在堆栈深处满足该条件时,您的处理程序就会被调用。
你将如何在 Rust 中实现这一点?
为了保持线程的独立性,您可以为条件处理程序使用线程本地存储(请参阅std::thread_local!)。每个条件都是一个堆栈的条件处理程序,要么只调用顶部的一个,要么从顶部开始一个迭代过程,直到一个成功为止。
那么,你会如何设置它们呢?
就个人而言,我会使用 RAII!我会在线程本地堆栈中绑定条件处理程序并将其注册到框架中(例如,使用侵入式双向链表作为堆栈)。
这样,当我完成后,条件处理程序会自动取消注册。
当然,系统必须考虑用户做意外的事情(比如将条件处理程序存储在堆中,而不是按照它们创建的顺序删除它们),这就是我们使用双向链表的原因,所以处理程序可以在必要时从堆栈中间取消注册。
所以我们有一个:
struct ConditionHandler<T> {
handler: T,
prev: Option<*mut ConditionHandler<T>>,
next: Option<*mut ConditionHandler<T>>,
}
“真正的”处理程序由用户作为T 传递。
这个处理程序是Sync吗?
可能,取决于您如何创建它,但您没有理由不能创建一个处理程序,从而无法在多个线程之间共享对它的引用。
注意:这些线程无法访问其 prev/next 数据成员,这些成员是私有的,不必是 Sync。
这个处理程序是Send吗?
除非特别注意,否则不会。
prev 和 next 字段不受并发访问保护,如果在另一个线程获得对它的引用时删除处理程序(例如,另一个处理程序试图取消注册自己),则更糟糕的是) 那么这个现在悬空的引用将导致未定义的行为。
注意:后一个问题意味着仅仅将Option<*mut Handler<T>> 切换为AtomicPtr<ConditionHandler<T>> 是不够的;有关详细信息,请参阅Common Pitfalls in Writing Lock-Free Algorithms。
你有它:如果T 是Sync,则ConditionHandler<T> 是Sync,但永远不会是Send(原样)。
为了完整起见,许多类型实现了Send,但没有实现Sync(实际上大多数Send 类型):例如Option 或Vec。