博客文章中,作者表明 Rust 不允许通过引用捕获变量:

use std::thread;
use std::sync::atomic::{AtomicI32, Ordering};

struct ThreadSafeCounter {
    count: AtomicI32,
}

impl ThreadSafeCounter {
    fn increment(&mut self) { self.count.fetch_add(1, Ordering::SeqCst); }
}

pub fn main() {
    let n = 10;
    let mut counter = ThreadSafeCounter { count: AtomicI32::new(0) };
    let mut threads = Vec::new();
    for _ in 0..n {
        threads.push(thread::spawn( || {
            // Rust won't allow this.  We are attempting to mutably borrow
            // the same value multiple times.
            counter.increment();
        }));
    }
    for thread in threads { thread.join(); }
    println!("{}", counter.count.load(Ordering::SeqCst));
}

然后对代码进行一处更改,并说道:

Rust 对线程安全的回答是允许对不可变引用进行修改。Rust 将此称为“内部可变性”。只需进行一处小改动,前面的示例即可编译并按预期运行:

use std::thread;
use std::sync::atomic::{AtomicI32, Ordering};

struct ThreadSafeCounter {
    count: AtomicI32,
}

impl ThreadSafeCounter {
    // increment() uses "interior mutability": it accepts an immutable
    // reference, but ultimately mutates the value.
    fn increment(&self) { self.count.fetch_add(1, Ordering::SeqCst); }
}

pub fn main() {
    let n = 10;
    let mut counter = ThreadSafeCounter { count: AtomicI32::new(0) };
    let mut threads = Vec::new();
    for _ in 0..n {
        threads.push(thread::spawn( || {
            counter.increment();
        }));
    }
    for thread in threads { thread.join(); }
    println!("{}", counter.count.load(Ordering::SeqCst));
}

我收到的错误是:

要强制闭包取得所有权counter(以及任何其他引用的变量),请使用move关键字:

这应该编译吗?该帖子写于 2021 年 12 月 18 日。


最佳答案
1

counter这篇博文是不正确的。由于生命周期:存在于 的堆栈中,因此该代码无法在任何 Rust 版本或版本(2015、2018 或 2021)下编译,main因此它最终会超出范围。生成的线程在执行时需要访问。这是由的参数'static的边界强制执行的。借用检查器的静态分析无法看到线程counter在返回之前完成使用并连接main

使其编译的最直接的方法是将计数器的move新引用放入每个生成的线程中。

use std::thread;
use std::sync::{atomic::{AtomicI32, Ordering}, Arc};

struct ThreadSafeCounter {
    count: AtomicI32,
}

impl ThreadSafeCounter {
    // increment() uses "interior mutability": it accepts an immutable
    // reference, but ultimately mutates the value.
    fn increment(&self) { self.count.fetch_add(1, Ordering::SeqCst); }
}

pub fn main() {
    let n = 10;
    let mut counter = Arc::new(ThreadSafeCounter { count: AtomicI32::new(0) });
    let mut threads = Vec::new();
    for _ in 0..n {
        let counter = Arc::clone(&counter);
        threads.push(thread::spawn(move || {
            counter.increment();
        }));
    }
    for thread in threads { thread.join(); }
    println!("{}", counter.count.load(Ordering::SeqCst));
}

另一种编译方法是使用。这是标准库的最新成员,它们允许通过强制连接所有生成的线程然后再返回给调用者,从调用者的堆栈中借用变量。

let counter = ThreadSafeCounter { count: AtomicI32::new(0) };
thread::scope(|scope| {
    for _ in 0..n {
        scope.spawn( || {
            counter.increment();
        });
    }
});
println!("{}", counter.count.load(Ordering::SeqCst));

在编写线程代码时,您更有可能看到普通线程交换消息或共享引用计数数据,而不是看到作用域线程借用数据。这是因为普通线程更灵活并且支持线程池。