在线程之间共享对特征实例的引用

fra*_*nza 11 rust

我正在使用Rust的并发性并尝试绕过Send/ Sync/ Arc/ Mutex.我在共享对特征实例的引用时遇到问题,该实例存在于HashMap:

use std::{collections::HashMap, sync::Arc, thread, time::Duration};

#[derive(Debug)]
struct A {
    foo: u8,
}

trait Foo {
    fn get_foo(&self) -> u8;
}

impl Foo for A {
    fn get_foo(&self) -> u8 {
        self.foo
    }
}

fn main() {
    let a = Arc::new(A { foo: 8 });

    let mut map: HashMap<u8, Arc<Foo>> = HashMap::new();
    map.insert(8u8, a);

    for _ in 0..2 {
        let a = map.get(&8u8).expect("boom");
        let a = a.clone();
        thread::spawn(move || {
            let _ = a.get_foo();
        });
    }
    thread::sleep(Duration::from_millis(200));
}
Run Code Online (Sandbox Code Playgroud)

(游乐场)

它给了我这些错误:

error[E0277]: `dyn Foo` cannot be sent between threads safely
  --> src/main.rs:27:9
   |
27 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^ `dyn Foo` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn Foo`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<dyn Foo>`
   = note: required because it appears within the type `[closure@src/main.rs:27:23: 29:10 a:std::sync::Arc<dyn Foo>]`
   = note: required by `std::thread::spawn`

error[E0277]: `dyn Foo` cannot be shared between threads safely
  --> src/main.rs:27:9
   |
27 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^ `dyn Foo` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `dyn Foo`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<dyn Foo>`
   = note: required because it appears within the type `[closure@src/main.rs:27:23: 29:10 a:std::sync::Arc<dyn Foo>]`
   = note: required by `std::thread::spawn`
Run Code Online (Sandbox Code Playgroud)

有人可以推荐一种方法来完成这项任务吗?我想我有点坚持使用Rust的方式来处理特征和线程.

Vla*_*eev 14

请记住,将删除转换为特征对象的原始值类型.因此,编译器无法知道里面的数据是否Arc<Foo>就是SendSync,没有这些特质在线程之间共享数据可能是不安全的.你需要指定其可以存储在类型Arc<Foo>必须SendSync:

let mut map: HashMap<u8, Arc<Foo + Sync + Send>> = HashMap::new();
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

Send绑定是必需的thread::spawn(),而且Sync是由需要Arc为它是Send.此外,thread::spawn()还需要,'static但它隐含在此特定Arc<Foo + Sync + Send>类型声明中.

当然,您只能存储SyncSend实现Foo,但这对于确保内存安全是必要的.但是,在Rust中,同步是用Mutex<T>或等包装器实现的RwLock<T>.Foo即使是T工具Foo,它们也不会实现,因此你将无法存储Mutex<Foo + Send>在你的地图中(除非Foo你的特性和你实现它Mutex<Foo>,这可能是笨重的),如果你的Foo实现不是,那将是必要的Sync但是Send(虽然我不确定我现在可以提供这种类型的例子).

要解决此问题,您需要更改地图类型以在其中明确包含互斥锁:

let mut map: HashMap<u8, Arc<Mutex<Foo + Send>>> = HashMap::new();
Run Code Online (Sandbox Code Playgroud)

这样一来,就没有必要为Sync界,因为MutexSync如果其内容Send.

当然,您将无法共享Foo完全没有的实现Send,并且无法解决它.例如,如果Foo实现包含Rcs ,则可能发生这种情况.

  • “A”是发送和同步,因为它不包含除“Send”或“Sync”以外的任何内容。这些特征会针对仅包含“Send”和“Sync”成员的所有类型自动实现,并且您的结构仅包含一个原始字段“Send”和“Sync”。 (2认同)

Thi*_*rry 8

弗拉基米尔在答案的前半部分为你的问题提供了一个解决方案:告诉Rust你的HashMap包含Foos SendSync.或者,您可以更改Foo自身的定义以包括这些特征边界:

trait Foo: Sync + Send {
    fn get_foo(&self) -> u8;
}
Run Code Online (Sandbox Code Playgroud)

因为struct A确实是SendSync,由于struct A确实执行trait Foo,当您使用的类型检查器不会抱怨Arc<A>作为Arc<Foo>.

如果不是共享对s的不可变(原子引用计数)引用,Foo而是希望共享对s的可变(原子引用计数)引用Foo,则需要控制对Foos的访问.这可以使用例如a来完成Mutex.由于Mutex然后将处理同步,因此可以删除Sync绑定Foo.例如:

use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

#[derive(Debug)]
struct A {
    foo: u8,
}

trait Foo: Send {
    fn get_foo(&self) -> u8;
}

impl Foo for A {
    fn get_foo(&self) -> u8 {
        self.foo
    }
}

fn main() {
    let a = Arc::new(Mutex::new(A { foo: 8 }));

    let mut map: HashMap<u8, Arc<Mutex<Foo>>> = HashMap::new();
    map.insert(8u8, a);

    for _ in 0..2 {
        let a = map.get(&8u8).expect("boom").clone();
        thread::spawn(move || {
            let result = a.lock().ok().expect("boom indeed").get_foo();
            println!("Result: {}", result);
        });
    }
    thread::sleep(Duration::from_millis(200));
}
Run Code Online (Sandbox Code Playgroud)

(游乐场)