【问题标题】:How can I mutably share an i32 between threads?如何在线程之间可变地共享 i32?
【发布时间】:2017-08-08 12:29:39
【问题描述】:

我是 Rust 和线程的新手,我正在尝试打印出一个数字,同时在另一个线程中添加它。我怎样才能做到这一点?

use std::thread;
use std::time::Duration;

fn main() {
    let mut num = 5;
    thread::spawn(move || {
        loop {
            num += 1;
            thread::sleep(Duration::from_secs(10));
        }
    });
    output(num);
}

fn output(num: i32) {
    loop {
        println!("{:?}", num);
        thread::sleep(Duration::from_secs(5));
    }
}

上面的代码不起作用:它总是打印5,就好像这个数字永远不会增加一样。

【问题讨论】:

    标签: multithreading rust


    【解决方案1】:

    请阅读"Shared-State Concurrency" chapter of The Rust Book,它详细解释了如何执行此操作。

    简而言之:

    1. 您的程序无法运行,因为num复制,因此output() 和线程对数字的不同副本进行操作。如果 num 不可复制,Rust 编译器将无法编译并出现错误。
    2. 由于需要在多个线程之间共享同一个变量,因此需要将其包装在 Arc 中(atomic reference-c变量)
    3. 由于需要修改Arc里面的变量,所以需要把它放到Mutex或者RwLock里面。您使用.lock() 方法从Mutex 中获取可变引用。该方法将确保在该可变引用的生命周期内对整个进程进行独占访问。
    use std::sync::{Arc, Mutex};
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        let num = Arc::new(Mutex::new(5));
        // allow `num` to be shared across threads (Arc) and modified
        // (Mutex) safely without a data race.
    
        let num_clone = num.clone();
        // create a cloned reference before moving `num` into the thread.
    
        thread::spawn(move || {
            loop {
                *num.lock().unwrap() += 1;
                // modify the number.
                thread::sleep(Duration::from_secs(10));
            }
        });
    
        output(num_clone);
    }
    
    fn output(num: Arc<Mutex<i32>>) {
        loop {
            println!("{:?}", *num.lock().unwrap());
            // read the number.
            //  - lock(): obtains a mutable reference; may fail,
            //    thus return a Result
            //  - unwrap(): ignore the error and get the real
            //    reference / cause panic on error.
            thread::sleep(Duration::from_secs(5));
        }
    }
    

    您可能还想阅读:

    【讨论】:

    • 对于一般类型TMutex&lt;T&gt; 确实是必要的。某些类型的另一种选择是在std::sync::atomic 中使用包装器类型,例如AtomicIsize,它提供了fetch_addcompare_and_swap 之类的方法。 (您还必须指定要强加的内存顺序。)
    【解决方案2】:

    另一个答案可以解决任何类型的问题,但作为 pnkfelix observes,原子包装器类型是另一种适用于 i32 特定情况的解决方案。

    从 Rust 1.0 开始,您可以使用 AtomicBoolAtomicPtr&lt;T&gt;AtomicIsizeAtomicUsize 来同步对 bool*mut Tisizeusize 值的多线程访问。在 Rust 1.34 中,几个新的 Atomic 类型已经稳定,包括 AtomicI32。 (查看std::sync::atomic 文档以获取当前列表。)

    使用原子类型很可能比锁定MutexRwLock 更有效,但需要更多注意内存排序的低级细节。如果您的线程共享的数据多于一种标准原子类型,您可能需要一个Mutex 而不是多个Atomics。

    也就是说,这是 kennytm 的答案版本,使用 AtomicI32 而不是 Mutex&lt;i32&gt;

    use std::sync::{
        atomic::{AtomicI32, Ordering},
        Arc,
    };
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        let num = Arc::new(AtomicI32::new(5));
        let num_clone = num.clone();
    
        thread::spawn(move || loop {
            num.fetch_add(1, Ordering::SeqCst);
            thread::sleep(Duration::from_secs(10));
        });
    
        output(num_clone);
    }
    
    fn output(num: Arc<AtomicI32>) {
        loop {
            println!("{:?}", num.load(Ordering::SeqCst));
            thread::sleep(Duration::from_secs(5));
        }
    }
    

    Arc 仍然是共享所有权所必需的(但请参阅How can I pass a reference to a stack variable to a thread?)。

    选择正确的内存Ordering 绝非易事。 SeqCst 是最保守的选择,但如果只共享一个内存地址,Relaxed 也应该可以工作。有关详细信息,请参阅下面的链接。

    链接

    1. std::sync::atomic module documentation
    2. AtomicsThe Rustonomicon 的章节
    3. LLVM Memory Model for Concurrent OperationsAtomic Instructions and Concurrency Guide

    【讨论】:

      猜你喜欢
      • 2014-09-03
      • 2021-01-27
      • 2019-05-01
      • 1970-01-01
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多