How do I create a global, mutable singleton?

前端 未结 3 1322
我在风中等你
我在风中等你 2020-11-21 06:59

What is the best way to create and use a struct with only one instantiation in the system? Yes, this is necessary, it is the OpenGL subsystem, and making multiple copies of

相关标签:
3条回答
  • 2020-11-21 07:25

    Non-answer answer

    Avoid global state in general. Instead, construct the object somewhere early (perhaps in main), then pass mutable references to that object into the places that need it. This will usually make your code easier to reason about and doesn't require as much bending over backwards.

    Look hard at yourself in the mirror before deciding that you want global mutable variables. There are rare cases where it's useful, so that's why it's worth knowing how to do.

    Still want to make one...?

    Using lazy-static

    The lazy-static crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

    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());
    }
    

    If you remove the Mutex then you have a global singleton without any mutability.

    You can also use a RwLock instead of a Mutex to allow multiple concurrent readers.

    Using once_cell

    The once_cell crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

    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());
    }
    

    If you remove the Mutex then you have a global singleton without any mutability.

    You can also use a RwLock instead of a Mutex to allow multiple concurrent readers.

    Using std::sync::SyncLazy

    The standard library is in the process of adding once_cell's functionality, currently called SyncLazy:

    #![feature(once_cell)] // 1.48.0-nightly (2020-08-28 d006f5734f49625c34d6)
    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());
    }
    
    

    If you remove the Mutex then you have a global singleton without any mutability.

    You can also use a RwLock instead of a Mutex to allow multiple concurrent readers.

    A special case: atomics

    If you only need to track an integer value, you can directly use an 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));
    }
    

    Manual, dependency-free implementation

    This is greatly cribbed from the Rust 1.0 implementation of stdin with some tweaks for modern Rust. You should also look at the modern implementation of io::Lazy. I've commented inline with what each line does.

    use std::sync::{Arc, Mutex, Once};
    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::new();
    
        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();
        }
    }
    

    This prints out:

    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
    

    This code compiles with Rust 1.42.0. The real implementations of Stdin use some unstable features to attempt to free the allocated memory, which this code does not.

    Really, you'd probably want to make SingletonReader implement Deref and DerefMut so you didn't have to poke into the object and lock it yourself.

    All of this work is what lazy-static or once_cell do for you.

    The meaning of "global"

    Please note that you can still use normal Rust scoping and module-level privacy to control access to a static or lazy_static variable. This means that you can declare it in a module or even inside of a function and it won't be accessible outside of that module / function. This is good for controlling access:

    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
    

    However, the variable is still global in that there's one instance of it that exists across the entire program.

    0 讨论(0)
  • 2020-11-21 07:41

    Use SpinLock for global access.

    #[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(
            // ...
        );
    }
    

    If you want mutable state(NOT Singleton), see What Not to Do in Rust for more descriptions.

    Hope it's helpful.

    0 讨论(0)
  • 2020-11-21 07:43

    Answering my own duplicate question .

    Cargo.toml:

    [dependencies]
    lazy_static = "1.4.0"
    

    Crate root ( lib.rs ):

    #[macro_use]
    extern crate lazy_static;
    

    Initialization ( no need for unsafe block ):

    /// EMPTY_ATTACK_TABLE defines an empty attack table, useful for initializing attack tables
    pub const EMPTY_ATTACK_TABLE: AttackTable = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ];
    
    lazy_static! {
        /// KNIGHT_ATTACK is the attack table of knight
        pub static ref KNIGHT_ATTACK: AttackTable = {
            let mut at = EMPTY_ATTACK_TABLE;
            for sq in 0..BOARD_AREA{
                at[sq] = jump_attack(sq, &KNIGHT_DELTAS, 0);
            }
            at
        };
        ...
    

    EDIT:

    Managed to solve it with once_cell, which does not need a macro.

    Cargo.toml:

    [dependencies]
    once_cell = "1.3.1"
    

    square.rs:

    use once_cell::sync::Lazy;
    
    ...
    
    /// AttackTable type records an attack bitboard for every square of a chess board
    pub type AttackTable = [Bitboard; BOARD_AREA];
    
    /// EMPTY_ATTACK_TABLE defines an empty attack table, useful for initializing attack tables
    pub const EMPTY_ATTACK_TABLE: AttackTable = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ];
    
    /// KNIGHT_ATTACK is the attack table of knight
    pub static KNIGHT_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
        let mut at = EMPTY_ATTACK_TABLE;
        for sq in 0..BOARD_AREA {
            at[sq] = jump_attack(sq, &KNIGHT_DELTAS, 0);
        }
        at
    });
    
    0 讨论(0)
提交回复
热议问题