【问题标题】:Rust - writing to indices of a vector across multiple threadsRust - 跨多个线程写入向量的索引
【发布时间】:2020-03-24 17:27:08
【问题描述】:

我有一个循环环形缓冲区(实现为向量),我希望一个线程定期写入环形缓冲区,另一个线程定期从环形缓冲区读取。只要访问向量的线程不在同一个索引处,是否可以创建一个可以同时读写的向量?

我希望达到的目标:

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

fn main() {

    let vec = Arc::new(vec![Mutex::new(1), Mutex::new(2),Mutex::new(3)]);

    {
        let vec = vec.clone();
        thread::spawn(move|| {
            let mut s2 = *vec.get_mut(2).unwrap().lock().unwrap();
            s2 = 7;
        });
    }

    println!("{}", vec[2].lock().unwrap());
}

编译器输出为:

Compiling playground v0.0.1 (/playground)
warning: variable `s2` is assigned to, but never used
  --> src/main.rs:12:21
   |
12 |             let mut s2 = *vec.get_mut(2).unwrap().lock().unwrap();
   |                     ^^
   |
   = note: `#[warn(unused_variables)]` on by default
   = note: consider using `_s2` instead

warning: value assigned to `s2` is never read
  --> src/main.rs:13:13
   |
13 |             s2 = 7;
   |             ^^
   |
   = note: `#[warn(unused_assignments)]` on by default
   = help: maybe it is overwritten before being read?

error[E0596]: cannot borrow data in an `Arc` as mutable
  --> src/main.rs:12:27
   |
12 |             let mut s2 = *vec.get_mut(2).unwrap().lock().unwrap();
   |                           ^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::sync::Arc<std::vec::Vec<std::sync::Mutex<i32>>>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

被 rust 类型系统试图阻止竞争条件挫败 :(

我不想要的

  • 一种涉及使锁定范围包括向量的实现。
  • 不能选择对矢量进行原子读写,因为矢量将包含图像。

游乐场链接:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5b5efe91bdd45c658d11f1cefb16045e

【问题讨论】:

  • 您是否考虑过不自己实现,而是使用 docs.rs/crossbeam/0.3.2/crossbeam/sync/chase_lev/index.html 之类的东西?不确定这是否适用于您的情况,但从 API 角度来看,这听起来像是您想要的。
  • 旁注:最好使用Arc::clone() 而不是arc.clone(),以免混淆您是克隆矢量还是弧。
  • 关于您的错误,您根本不需要 get_mut 向量。锁定互斥体不需要 &mut。
  • 关于您的问题:您的代码似乎与环形缓冲区没有任何相似之处。 “定期从环形缓冲区读取”是指它只是 读取,还是从环形缓冲区 poll(因此删除)某些内容?请澄清。
  • crossbeam crate 的最新版本中,ArrayQueue 提供了一个无锁环形缓冲区。

标签: multithreading rust thread-safety buffer


【解决方案1】:

首先,我建议您使用std::sync::RwLock,因为它允许多个阅读器同时读取数据。

其次,产生线程会导致代码出现性能瓶颈。尝试使用线程池。

当然,具体选择会因基准测试结果而异,但这些只是一般性建议。

您的代码大部分是正确的,除了一个关键部分。您正在使用 Mutex,它实现了 interior mutability 模式并提供线程安全。

内部可变性将XOR 借用规则(N 个不可变借用或仅一个可变借用)的编译时检查移至运行时。因此,Mutex 确保任何时候都只存在一位读者或一位作家。

当你尝试从vec 获取可变引用时,像这样

vec.get_mut(..)

您基本上忽略了内部可变性提供的好处。编译器不能保证XOR 规则没有被破坏,因为你借用vec 作为可变的。

明显的解决方案是将vec 借用为不可变,并使用Mutex 来防止竞争条件,并且不要使用编译器借用规则。

let mut s2 = vec
    .get(2) // Get immutable reference to second item
    .unwrap() // Ensure that it exists
    .lock() // Lock mutex. 
    .unwrap(); // Ensure mutex isn't poisoned.

// s2 is now `std::sync::MutexGuard<i32>`, which implements `std::ops::DerefMut`, 
// so it can get us mutable reference to data.
*s2 = 7;

【讨论】:

  • 如果您有多个阅读器访问您也尝试写入的同一位置,RwLock 可能会饿死作者,因为读取可能重叠。它不像“当你有多个读者时使用RwLock”那么简单。否则,很好的答案。
猜你喜欢
  • 2022-08-23
  • 2011-04-12
  • 2013-01-19
  • 2021-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-07
  • 1970-01-01
相关资源
最近更新 更多