首页 文章

如何创建一个全局的,可变的单例?

提问于
浏览
71

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

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

1 回答

  • 80

    Non-answer answer

    一般避免全球状态 . 相反,尽早在某处构建对象(可能在 main 中),然后将对该对象的可变引用传递到需要它的位置 . 这通常会使您的代码更易于推理,并且不需要向后弯曲 .

    在决定你想要全局可变变量之前,先在镜子里仔细看看 . 在极少数情况下它很有用,所以这就是为什么它值得知道怎么做 .

    还想做一个......?

    使用lazy-static

    lazy-static箱子可以带走一些创造单身人士的苦差事(下图) . 这是一个全局可变向量:

    #[macro_use]
    extern crate lazy_static;
    
    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());
    }
    

    如果你删除了 Mutex 那么你就有了一个没有任何可变性的全局单例 .

    一个特例:原子

    如果您只需要跟踪整数值,则可以直接使用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 . 您还应该看一下io::Lazy的现代实现 . 我已经评论了每行的内容 .

    use std::sync::{Arc, Mutex, Once, ONCE_INIT};
    use std::time::Duration;
    use std::{mem, thread};
    
    #[derive(Clone)]
    struct SingletonReader {
        // Since we will be used in many threads, we need to protect
        // concurrent access
        inner: Arc<Mutex<u8>>,
    }
    
    fn singleton() -> SingletonReader {
        // Initialize it to a null value
        static mut SINGLETON: *const SingletonReader = 0 as *const SingletonReader;
        static ONCE: Once = ONCE_INIT;
    
        unsafe {
            ONCE.call_once(|| {
                // Make it
                let singleton = SingletonReader {
                    inner: Arc::new(Mutex::new(0)),
                };
    
                // Put it in the heap so it can outlive this call
                SINGLETON = mem::transmute(Box::new(singleton));
            });
    
            // Now we give out a copy of the data that is safe to use concurrently.
            (*SINGLETON).clone()
        }
    }
    
    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.23.0编译 . Stdin 的实际实现使用一些不稳定的功能来尝试释放分配的内存,而这些代码没有 .

    真的,你可能想让 SingletonReader 实现DerefDerefMut,这样你就不必戳入对象并自己锁定它 .

    所有这些工作都是lazy-static为您所做的 .

相关问题