在系统中创建和使用只有一个实例化的结构的最佳方法是什么?是的,这是必要的,它是OpenGL子系统,复制多个副本并到处传递只会增加混乱,而不是缓解混乱。

单例需要尽可能的高效。在静态区域中存储任意对象似乎是不可能的,因为它包含一个带析构函数的Vec。第二个选项是在静态区域存储一个(不安全的)指针,指向一个堆分配的单例。在保持语法简洁的同时,最方便、最安全的方法是什么?


当前回答

使用SpinLock进行全局访问。

#[derive(Default)]
struct ThreadRegistry {
    pub enabled_for_new_threads: bool,
    threads: Option<HashMap<u32, *const Tls>>,
}

impl ThreadRegistry {
    fn threads(&mut self) -> &mut HashMap<u32, *const Tls> {
        self.threads.get_or_insert_with(HashMap::new)
    }
}

static THREAD_REGISTRY: SpinLock<ThreadRegistry> = SpinLock::new(Default::default());

fn func_1() {
    let thread_registry = THREAD_REGISTRY.lock();  // Immutable access
    if thread_registry.enabled_for_new_threads {
    }
}

fn func_2() {
    let mut thread_registry = THREAD_REGISTRY.lock();  // Mutable access
    thread_registry.threads().insert(
        // ...
    );
}

如果你想要可变状态(不是单例),请参阅Rust中不能做什么以获得更多描述。

希望对大家有帮助。

其他回答

我有限的解决方案是定义一个结构体,而不是一个全局可变的。要使用该结构体,外部代码需要调用init(),但是通过使用AtomicBoolean(用于多线程使用),我们不允许多次调用init()。

static INITIATED: AtomicBool = AtomicBool::new(false);

struct Singleton {
  ...
}

impl Singleton {
  pub fn init() -> Self {
    if INITIATED.load(Ordering::Relaxed) {
      panic!("Cannot initiate more than once")
    } else {
      INITIATED.store(true, Ordering::Relaxed);

      Singleton {
        ...
      }
    }
  }
}

fn main() {
  let singleton = Singleton::init();
  
  // panic here
  // let another_one = Singleton::init();
  ...
}

有点晚了,但以下是我如何解决这个问题(rust 1.66-nightly):

#![feature(const_size_of_val)]
#![feature(const_ptr_write)]

static mut GLOBAL_LAZY_MUT: StructThatIsNotSyncNorSend = unsafe {
    // Copied from MaybeUninit::zeroed() with minor modifications, see below
    let mut u = MaybeUninit::uninit();

    let bytes = mem::size_of_val(&u);
    write_bytes(u.as_ptr() as *const u8 as *mut u8, 0xA5, bytes); //Trick the compiler check that verifies pointers and references are not null.

    u.assume_init()
};

(...)

fn main() {
    unsafe {
        let mut v = StructThatIsNotSyncNorSend::new();
        mem::swap(&mut GLOBAL_LAZY_MUT, &mut v);
        mem::forget(v);
    }
  
}

注意,这段代码非常不安全,如果处理不当,很容易变成UB。

你现在有了一个全局静态的!Send !Sync值,没有互斥锁的保护。如果从多个线程访问它,即使只是为了读取,它也是UB。如果你不按显示的方式初始化它,它就是UB,因为它在一个实际的值上调用Drop。

你只是让rust编译器相信某个UB不是UB。你刚刚确信在全局静态中放入!Sync和!Send是可以的。

如果不确定,请不要使用此代码片段。

如果你是在夜间,你可以使用LazyLock。

它或多或少做了箱子once_cell和lazy_sync做的事情。这两个板条箱是非常常见的,所以有一个很好的机会,他们可能已经在你的货物。锁定依赖树。但如果你更喜欢“冒险”一点,选择LazyLock,在它变得稳定之前,它(就像所有的东西一样)可能会发生变化。

(注意:直到最近std::sync::LazyLock被命名为std::lazy::SyncLazy,但最近被重新命名。)

《Rust》中不该做的事

重述一下:在对象发生变化时使用内部可变性 它的内部状态,考虑使用一个模式来提升new 当前状态为旧状态,当前消费者为旧状态 通过在RwLock中放入Arc来继续持有它。

use std::sync::{Arc, RwLock};

#[derive(Default)]
struct Config {
    pub debug_mode: bool,
}

impl Config {
    pub fn current() -> Arc<Config> {
        CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
    }
    pub fn make_current(self) {
        CURRENT_CONFIG.with(|c| *c.write().unwrap() = Arc::new(self))
    }
}

thread_local! {
    static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}

fn main() {
    Config { debug_mode: true }.make_current();
    if Config::current().debug_mode {
        // do something
    }
}

使用SpinLock进行全局访问。

#[derive(Default)]
struct ThreadRegistry {
    pub enabled_for_new_threads: bool,
    threads: Option<HashMap<u32, *const Tls>>,
}

impl ThreadRegistry {
    fn threads(&mut self) -> &mut HashMap<u32, *const Tls> {
        self.threads.get_or_insert_with(HashMap::new)
    }
}

static THREAD_REGISTRY: SpinLock<ThreadRegistry> = SpinLock::new(Default::default());

fn func_1() {
    let thread_registry = THREAD_REGISTRY.lock();  // Immutable access
    if thread_registry.enabled_for_new_threads {
    }
}

fn func_2() {
    let mut thread_registry = THREAD_REGISTRY.lock();  // Mutable access
    thread_registry.threads().insert(
        // ...
    );
}

如果你想要可变状态(不是单例),请参阅Rust中不能做什么以获得更多描述。

希望对大家有帮助。