如何创建一个全局的、可变的单例? [英] How do I create a global, mutable singleton?

查看:96
本文介绍了如何创建一个全局的、可变的单例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 this and passing it around everywhere would add confusion, rather than relieve it.

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

The singleton needs to be as efficient as possible. It doesn't seem possible to store an arbitrary object on the static area, as it contains a Vec with a destructor. The second option is to store an (unsafe) pointer on the static area, pointing to a heap allocated singleton. What is the most convenient and safest way to do this, while keeping syntax terse.

推荐答案

Non-answer answer

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

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.

还想做一个...?

在以下 3 个解决方案中:

In the 3 following solutions:

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

lazy-static crate 可以消除手动创建单例的一些苦差事.这是一个全局可变向量:

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

使用once_cell

once_cell crate 可以消除手动创建单例的一些苦差事.这是一个全局可变向量:

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

使用std::sync::SyncLazy

标准库正在过程添加once_cell 的功能,目前称为 SyncLazy:

Using std::sync::SyncLazy

The standard library is in the process of adding once_cell's functionality, currently called 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));
}

手动、无依赖的实现

这是从 stdin 的 Rust 1.0 实现,对现代 Rust 进行了一些调整.您还应该查看 的现代实现io::Lazy.我已经对每一行的作用进行了内联注释.

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

打印出来:

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.42.0 编译.Stdin 的实际实现使用了一些不稳定的特性来尝试释放分配的内存,而本代码没有这样做.

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.

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

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.

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

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

请注意,您仍然可以使用正常的 Rust 范围和模块级隐私来控制对 staticlazy_static 变量的访问.这意味着您可以在模块中甚至在函数内部声明它,并且在该模块/函数之外无法访问它.这有利于控制访问:

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.

这篇关于如何创建一个全局的、可变的单例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆