rust - 如何创建一个全局的、可变的单例?

标签 rust

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

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

最佳答案

不回答的回答
一般避免全局状态。相反,尽早在某处构造对象(可能在 main 中),然后将对该对象的可变引用传递到需要它的地方。这通常会使您的代码更容易推理,并且不需要向后弯曲太多。
在决定是否需要全局可变变量之前,请仔细看看镜子中的自己。在极少数情况下它很有用,所以这就是为什么值得知道如何去做。
还想做一个……?
尖端
在以下 3 个解决方案中:

  • 如果您删除 Mutex 那么你有一个 没有任何可变性的全局单例 .
  • 您也可以使用 RwLock 而不是 Mutex允许多个并发读者 .

  • 使用 lazy-staticlazy-static crate 可以消除手动创建单例的一些苦差事。这是一个全局可变向量:
    use lazy_static::lazy_static; // 1.4.0
    use std::sync::Mutex;
    
    lazy_static! {
        static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
    }
    
    fn do_a_call() {
        ARRAY.lock().unwrap().push(1);
    }
    
    fn main() {
        do_a_call();
        do_a_call();
        do_a_call();
    
        println!("called {}", ARRAY.lock().unwrap().len());
    }
    
    使用 once_cellonce_cell crate 可以消除手动创建单例的一些苦差事。这是一个全局可变向量:
    use once_cell::sync::Lazy; // 1.3.1
    use std::sync::Mutex;
    
    static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| Mutex::new(vec![]));
    
    fn do_a_call() {
        ARRAY.lock().unwrap().push(1);
    }
    
    fn main() {
        do_a_call();
        do_a_call();
        do_a_call();
    
        println!("called {}", ARRAY.lock().unwrap().len());
    }
    
    使用 std::sync::SyncLazy标准库位于 the process添加 once_cell的功能,目前称为 SyncLazy :
    #![feature(once_cell)] // 1.53.0-nightly (2021-04-01 d474075a8f28ae9a410e)
    use std::{lazy::SyncLazy, sync::Mutex};
    
    static ARRAY: SyncLazy<Mutex<Vec<u8>>> = SyncLazy::new(|| Mutex::new(vec![]));
    
    fn do_a_call() {
        ARRAY.lock().unwrap().push(1);
    }
    
    fn main() {
        do_a_call();
        do_a_call();
        do_a_call();
    
        println!("called {}", ARRAY.lock().unwrap().len());
    }
    
    一个特例:原子
    如果只需要跟踪一个整数值,可以直接使用atomic :
    use std::sync::atomic::{AtomicUsize, Ordering};
    
    static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
    
    fn do_a_call() {
        CALL_COUNT.fetch_add(1, Ordering::SeqCst);
    }
    
    fn main() {
        do_a_call();
        do_a_call();
        do_a_call();
    
        println!("called {}", CALL_COUNT.load(Ordering::SeqCst));
    }
    
    手动、无依赖的实现
    有几种现有的静态实现,例如 the Rust 1.0 implementation of stdin .这是适应现代 Rust 的相同想法,例如使用 MaybeUninit以避免分配和不必要的间接。您还应该查看 io::Lazy 的现代实现。 .我已经对每行的功能进行了内联评论。
    use std::sync::{Mutex, Once};
    use std::time::Duration;
    use std::{mem::MaybeUninit, thread};
    
    struct SingletonReader {
        // Since we will be used in many threads, we need to protect
        // concurrent access
        inner: Mutex<u8>,
    }
    
    fn singleton() -> &'static SingletonReader {
        // Create an uninitialized static
        static mut SINGLETON: MaybeUninit<SingletonReader> = MaybeUninit::uninit();
        static ONCE: Once = Once::new();
    
        unsafe {
            ONCE.call_once(|| {
                // Make it
                let singleton = SingletonReader {
                    inner: Mutex::new(0),
                };
                // Store it to the static var, i.e. initialize it
                SINGLETON.write(singleton);
            });
    
            // Now we give out a shared reference to the data, which is safe to use
            // concurrently.
            SINGLETON.assume_init_ref()
        }
    }
    
    fn main() {
        // Let's use the singleton in a few threads
        let threads: Vec<_> = (0..10)
            .map(|i| {
                thread::spawn(move || {
                    thread::sleep(Duration::from_millis(i * 10));
                    let s = singleton();
                    let mut data = s.inner.lock().unwrap();
                    *data = i as u8;
                })
            })
            .collect();
    
        // And let's check the singleton every so often
        for _ in 0u8..20 {
            thread::sleep(Duration::from_millis(5));
    
            let s = singleton();
            let data = s.inner.lock().unwrap();
            println!("It is: {}", *data);
        }
    
        for thread in threads.into_iter() {
            thread.join().unwrap();
        }
    }
    
    这打印出来:
    It is: 0
    It is: 1
    It is: 1
    It is: 2
    It is: 2
    It is: 3
    It is: 3
    It is: 4
    It is: 4
    It is: 5
    It is: 5
    It is: 6
    It is: 6
    It is: 7
    It is: 7
    It is: 8
    It is: 8
    It is: 9
    It is: 9
    It is: 9
    
    此代码使用 Rust 1.55.0 编译。
    所有这些工作都是lazy-static 或 once_cell 为你做的。
    “全局”的含义
    请注意,您仍然可以使用普通的 Rust 范围和模块级隐私来控制对 static 的访问。或 lazy_static多变的。这意味着您可以在模块中甚至在函数内部声明它,并且在该模块/函数之外无法访问它。这有利于控制访问:
    use lazy_static::lazy_static; // 1.2.0
    
    fn only_here() {
        lazy_static! {
            static ref NAME: String = String::from("hello, world!");
        }
        
        println!("{}", &*NAME);
    }
    
    fn not_here() {
        println!("{}", &*NAME);
    }
    
    error[E0425]: cannot find value `NAME` in this scope
      --> src/lib.rs:12:22
       |
    12 |     println!("{}", &*NAME);
       |                      ^^^^ not found in this scope
    
    但是,该变量仍然是全局的,因为它的一个实例存在于整个程序中。

    关于rust - 如何创建一个全局的、可变的单例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65220777/

    相关文章:

    rust - 如何在 Rust 中交换字符串中的两个字符

    rust - 如何使用 Arc<Mutex<...>> 转换特征对象?

    macros - 我如何在稳定的 Rust 中使用宏值作为函数名的一部分?

    generics - 为什么我不能在带有类型参数的特征上添加一揽子实现?

    collections - 我怎样才能拥有一组因关联类型不同而不同的对象?

    tcp - 用 Rust 编写的示例 TCP 服务器

    rust - 如何有效地在Rust中为u64整数找到2的下一个幂?

    time - 如何将 JWT 过期日期的 time::Tm 转换为 u64

    rust - 无法在使用rust 柴油中使用通用参数实现特征

    rust - 为什么没有为明确实现的类型实现特征?