我希望线程之间有一个共享 struct . struct 中有许多字段从未被修改过,还有一个HashMap.我不想一次更新/删除就锁定整个HashMap,所以我的HashMap看起来像HashMap<u8, Mutex<u8>>.这是可行的,但毫无意义,因为线程无论如何都会锁定整个 map .

这是一个工作版本,没有线程;我认为这个例子没有必要.

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

fn main() {
    let s = Arc::new(Mutex::new(S::new()));
    let z = s.clone();
    let _ = z.lock().unwrap();
}

struct S {
    x: HashMap<u8, Mutex<u8>>, // other non-mutable fields
}

impl S {
    pub fn new() -> S {
        S {
            x: HashMap::default(),
        }
    }
}

Playground

这有可能吗?文件中有什么明显的遗漏吗?

我一直在努力让它工作,但我不知道怎么做.基本上,我看到的每个例子都有一个Mutex(或RwLock,或类似的数字)来保护内在价值.

推荐答案

我不明白你的请求是怎么可能的,至少如果没有一些非常聪明的无锁数据 struct ;如果多个线程需要插入散列到同一位置的新值,该怎么办?

在之前的工作中,我使用了RwLock<HashMap<K, Mutex<V>>>.当向散列中插入一个值时,会得到一个短时间的独占锁.剩下的时间里,你可以有多个线程,读卡器锁定HashMap,从而锁定一个给定的元素.如果他们需要对数据进行变异,他们可以独占访问Mutex.

下面是一个例子:

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

fn main() {
    let data = Arc::new(RwLock::new(HashMap::new()));

    let threads: Vec<_> = (0..10)
        .map(|i| {
            let data = Arc::clone(&data);
            thread::spawn(move || worker_thread(i, data))
        })
        .collect();

    for t in threads {
        t.join().expect("Thread panicked");
    }

    println!("{:?}", data);
}

fn worker_thread(id: u8, data: Arc<RwLock<HashMap<u8, Mutex<i32>>>>) {
    loop {
        // Assume that the element already exists
        let map = data.read().expect("RwLock poisoned");

        if let Some(element) = map.get(&id) {
            let mut element = element.lock().expect("Mutex poisoned");

            // Perform our normal work updating a specific element.
            // The entire HashMap only has a read lock, which
            // means that other threads can access it.
            *element += 1;
            thread::sleep(Duration::from_secs(1));

            return;
        }

        // If we got this far, the element doesn't exist

        // Get rid of our read lock and switch to a write lock
        // You want to minimize the time we hold the writer lock
        drop(map);
        let mut map = data.write().expect("RwLock poisoned");

        // We use HashMap::entry to handle the case where another thread 
        // inserted the same key while where were unlocked.
        thread::sleep(Duration::from_millis(50));
        map.entry(id).or_insert_with(|| Mutex::new(0));
        // Let the loop start us over to try again
    }
}

在我的机器上运行大约需要2.7秒,即使它启动10个线程,每个线程在保持对元素数据的独占锁的同时等待1秒.

然而,这个解决方案并非没有问题.当对一个主锁存在大量争用时,获取写锁可能需要一段时间,并完全 destruct 并行性.

在这种情况下,你可以切换到RwLock<HashMap<K, Arc<Mutex<V>>>>.一旦有了读或写锁,就可以克隆值的Arc,返回它并解锁hashmap.

下一步是使用arc-swap这样的 crate ,上面写着:

然后锁定、克隆[RwLock<Arc<T>>]并解锁.这会受到CPU级别争用(在Arc的锁和引用计数上)的影响,这使得它相对较慢.根据具体的实现,一个更新可能会被稳定的读卡器阻塞任意长时间.

可以改用ArcSwap,这解决了上述问题,并且在竞争和非竞争场景中都比RwLock具有更好的性能特征.

我经常主张执行某种更智能的算法.例如,你可以旋转N个线程,每个线程有自己的HashMap个线程.然后你就可以在他们中间工作了.例如,对于上面的简单示例,可以使用id % N_THREADS.还有一些复杂的切分方案取决于您的数据.

AsGo在传福音方面做得很好:do not communicate by sharing memory; instead, share memory by communicating人.

Rust相关问答推荐

在Tauri中获取ICoreWebView 2_7以打印PDF

为什么单元类型(空元组)实现了`Extend`trait?

如何从接收&;self的方法克隆RC

有没有更好的方法从HashMap的条目初始化 struct ?

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

重写Rust中的方法以使用`&;mut self`而不是`mut self`

告诉Rust编译器返回值不包含构造函数中提供的引用

当对VEC;U8>;使用serde_json时,Base64编码是保护空间的好方法吗?

为什么TcpListener的文件描述符和生成的TcpStream不同?

AXUM一路由多个不包括URL的参数类型

为什么将易错函数的泛型结果作为泛型参数传递 infer ()?不应该是暧昧的吗?

提取指向特征函数的原始指针

结果流到 Vec 的结果:如何避免多个into_iter和collect?

将 &str 或 String 保存在变量中

str 和 String 的 Rust 生命周期

使用 Rust 从 Raspberry Pi Pico 上的 SPI 读取值

如何在 Rust 的 Hyper 异步闭包中从外部范围正确读取字符串值

Rust 中的通用 From 实现

如何用另一个变量向量置换 rust simd 向量?

类型参数不受 impl 特征、自身类型或谓词的约束