如何修改一个线程中的值并使用共享内存读取另一个线程中的值?

How do I modify a value in one thread and read the value in another thread using shared memory?

以下Python代码创建了一个线程(实际上是一个进程),其中包含传递给它的两个浮点数的数组,线程每 5 秒第一个浮点数增加 1,第二个浮点数增加 -1 ,而主线程不断打印两个浮点数:

from multiprocessing import Process, Array
from time import sleep

def target(states):
    while True:
        states[0] -= 1
        states[1] += 1
        sleep(5)

def main():
    states = Array("d", [0.0, 0.0])
    process = Process(target=target, args=(states,))
    process.start()
    while True:
        print(states[0])
        print(states[1])

if __name__ == "__main__":
    main()

如何在 Rust 中使用共享内存做同样的事情?我已尝试执行以下操作 (playground):

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

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let data = data.clone();
    thread::spawn(move || {
        let mut data = data.lock().unwrap();
        data[0] = 1.0;
    });
    print!("{}", data[0]);
}

但这会导致编译错误:

error: cannot index a value of type `std::sync::Arc<std::sync::Mutex<[_; 1]>>`
  --> <anon>:12:18
   |>
12 |>     print!("{}", data[0]);
   |>                  ^^^^^^^

即使那行得通,它也会做一些不同的事情。我已经阅读了 this,但我仍然不知道该怎么做。

好的,让我们首先修复编译器错误:

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

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let thread_data = data.clone();
    thread::spawn(move || {
        let mut data = thread_data.lock().unwrap();
        data[0] = 1.0;
    });
    println!("{}", data.lock().unwrap()[0]);
}

变量thread_data总是被移动到线程中,这就是为什么它不能在线程产生后被访问。 但这仍然有一个问题:您正在启动一个线程,该线程将 运行 与主线程并发,并且最后一个打印语句将在线程大多数时间更改值之前执行(它将是随机的)。

要解决此问题,您必须在打印值之前等待线程完成:

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

fn main() {
    let data = Arc::new(Mutex::new([0.0]));
    let thread_data = data.clone();
    let t = thread::spawn(move || {
        let mut data = thread_data.lock().unwrap();
        data[0] = 1.0;
    });
    t.join().unwrap();
    println!("{}", data.lock().unwrap()[0]);
}

这将始终产生正确的结果。

您的代码离您不远了! :)

让我们先看看编译器错误:它说您显然正在尝试索引某些东西。没错,你想索引data变量(带data[0]),但是编译器报错说你要索引的值是std::sync::Arc<std::sync::Mutex<[_; 1]>>类型,无法索引。

如果您查看类型,您很快就会发现:我的数组仍然包裹在 Mutex<T> 中,而 Mutex<T> 又包裹在 Arc<T> 中。这给我们带来了解决方案:您也必须锁定读取权限。所以你必须像在另一个线程中一样添加 lock().unwrap()

print!("{}", data.lock().unwrap()[0]);

但是现在出现了一个新的编译器错误:use of moved value: `data`。该死!这来自你的名字阴影。你在开始线程之前说 let data = data.clone(); ;这遮盖了原来的 data。那么我们用 let data_for_thread = data.clone() 替换它并在另一个线程中使用 data_for_thread 怎么样?你可以看到工作结果 here on the playground.


让它做与 Python 示例相同的事情不再那么难了,是吗?

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

let data = Arc::new(Mutex::new([0.0, 0.0]));
let data_for_thread = data.clone();
thread::spawn(move || {
    loop {
        thread::sleep(Duration::from_secs(5))
        let mut data = data_for_thread.lock().unwrap();
        data[0] += 1.0;
        data[1] -= 1.0;
    }
});

loop {
    let data = data.lock().unwrap();
    println!("{}, {}", data[0], data[1]);
}

你可以试试 here on the playground,尽管我更改了一些小东西以允许 运行 在操场上。

如果您通过一个线程更新公共数据,其他线程可能看不到更新后的值,除非您执行以下操作:

  1. 将变量声明为 volatile 以确保将最新更新返回给读取该变量的线程。数据是从内存块中读取的,而不是从缓存中读取的。

  2. 使所有更新和读取同步,这在性能方面可能代价高昂,但由于写入和读取的非同步方法,肯定会处理数据 corruptions/in-consistency通过不同的线程。