如何在 Rust 结构体内创建线程本地变量?

6

我需要一个线程本地变量,最好是存储在一个结构体中,该结构体当前存储了我的程序的大部分全局状态。

我能想到的第一种方法是使用 thread_local! 宏,但我希望将其保留在线程局部状态结构体中。

我能想到的第二种方法是在线程和线程本地变量值之间使用类似于 HashMap<Thread,MyThreadLocalData> 的结构。然后,我会使用 thread::current 进行查找适当的值。

我应该提到的最后一个要求是,给定程序中并非所有线程都是由 Rust 代码创建的,但 Rust 代码可以在任何线程上运行,因此解决方案应该具有鲁棒性。

是否有一种最佳实践的方法来做这件事?也许有一个 threadId 可以让我使用简单的 Vec 而不是 HashMap,或避免哈希开销?是否有相应的库可用?

另一种选择是修改每个可能在多线程环境中使用的函数的参数,以接受状态结构体和线程本地状态结构体,但这不太容易与非由 Rust 创建的线程一起工作。

2个回答

9

在结构体中使用线程本地变量可以通过将其放置在impl块中来实现:

use std::cell::RefCell;

struct Foo;
impl Foo {
    thread_local! {
        // Could add pub to make it public to whatever Foo already is public to.
        static FOO: RefCell<usize> = RefCell::new(0);
    }
}

并且可以使用 Foo::FOO 进行访问:

Foo::FOO.with(|x| println!("{:?}", x));
Playground请注意,访问它必须使用Foo::前缀, 因为它不是字段,而是一个关联的static
也可以通过存储对它的引用来实现:
use std::cell::RefCell;
use std::thread::LocalKey;

thread_local! {
    // Note lack of pub
    static FOO: RefCell<usize> = RefCell::new(0);
}
struct Bar {
    // Visibility here changes what can see `foo`.
    foo: &'static LocalKey<RefCell<usize>>,
    // Rest of your data.
}
impl Bar {
    fn constructor() -> Self {
        Self {
            foo: &FOO,
            // Rest of your data.
        }
    }
}

这句话的意思是只有在结构体有一个实例时才能正常运作,这样说正确吗?是否有办法允许多个结构体实例,或者这已经算是另一个问题了? - PiRocks
2
这将适用于任何数量的结构实例。如果您只想要一个结构实例,那么您的结构本身必须放在线程本地变量中。第一种方法不依赖于结构实例的存在,而第二种方法则依赖于它。 - Optimistic Peach
你只能在with方法的闭包中访问静态值。要从with方法中获取该值,您必须克隆它。有关thread_local代码中的注释,请参阅参考文献! - GuiTaek

4

最简单的方法是使用thread_local crate

struct Foo {
    thread_counter: ThreadLocal<AtomicU64>,
}

impl Foo {
    fn new() -> Foo {
        Foo {
            thread_counter: ThreadLocal::new(),
        }
    }
}

let foo = Foo::new();
let count = foo
    .thread_counter
    .get_or(|| AtomicU64::new(START_VALUE))
    .fetch_add(1, Ordering::Relaxed);

不幸的是,这不会设置全局值,这正是我搜索和理解 OP 想要的。 - GuiTaek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接