如何创建和使用只有一个实例的结构体是最好的呢?是的,这是必要的,因为它是OpenGL子系统,如果制作多个副本并到处传递会增加混乱而不是减轻。
这个单实例需要尽可能高效。似乎不可能将任意对象存储在静态区域中,因为它包含具有析构函数的Vec
。第二个选项是在静态区域上存储一个(不安全的)指针,指向堆分配的单实例。在保持语法简洁的同时,最方便和最安全的方法是什么?
如何创建和使用只有一个实例的结构体是最好的呢?是的,这是必要的,因为它是OpenGL子系统,如果制作多个副本并到处传递会增加混乱而不是减轻。
这个单实例需要尽可能高效。似乎不可能将任意对象存储在静态区域中,因为它包含具有析构函数的Vec
。第二个选项是在静态区域上存储一个(不安全的)指针,指向堆分配的单实例。在保持语法简洁的同时,最方便和最安全的方法是什么?
一般情况下,避免使用全局状态。相反,可以在早期的某个地方(例如在main
函数中)构建对象,然后将可变引用传递给需要它的地方。这通常会使您的代码更易于理解,并且不需要过多的弯腰曲背。
在决定是否需要全局可变变量之前,请在镜子前认真审视自己。虽然很少见,但有些情况下它是有用的,所以了解如何使用是值得的。
还是想要创建一个吗...?
在以下解决方案中:
std::sync::OnceLock
OnceLock
在 Rust 1.70.0 版本中已经稳定下来。您可以使用它来获得一个无依赖的实现:use std::sync::{Mutex, OnceLock};
fn array() -> &'static Mutex<Vec<u8>> {
static ARRAY: OnceLock<Mutex<Vec<u8>>> = OnceLock::new();
ARRAY.get_or_init(|| 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());
}
LazyLock
仍然不稳定,但可以消除array()
辅助函数。
lazy-static
lazy-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_cell
once_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::LazyLock
标准库正在进行中添加once_cell
的功能,目前称为LazyLock
:
#![feature(lazy_cell)]
use std::sync::{LazyLock, Mutex};
static ARRAY: LazyLock<Mutex<Vec<u8>>> = LazyLock::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());
}
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));
}
已经存在几种静态实现,比如Rust 1.0版本的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
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
lazy_static
示例,它完美地工作了。这里没有任何“外部静态”变量。也许您需要在您的端上检查一下,以确保您已经完全理解了答案。 - ShepmasterMutex::new
是const
,因此您可以使用全局静态Mutex
锁,而无需进行懒加载:use std::sync::Mutex;
static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
fn main() {
GLOBAL_DATA.lock().unwrap().push(42);
println!("{:?}", GLOBAL_DATA.lock().unwrap());
}
Vec::new
是const
的事实。如果您需要使用非const
函数来设置单例模式,则可以将数据包装在Option
中,并最初将其设置为None
。这使您可以使用数据结构,例如Hashset
,目前无法在const
上下文中使用:use std::sync::Mutex;
use std::collections::HashSet;
static GLOBAL_DATA: Mutex<Option<HashSet<i32>>> = Mutex::new(None);
fn main() {
*GLOBAL_DATA.lock().unwrap() = Some(HashSet::from([42]));
println!("V2: {:?}", GLOBAL_DATA.lock().unwrap());
}
或者,您可以使用RwLock代替Mutex
,因为自Rust 1.63起,RwLock::new
也是const
。这将使多个线程同时读取数据成为可能。
如果您需要使用非const
函数进行初始化,并且不想使用Option
,则可以使用类似once_cell或lazy-static的crate进行延迟初始化,如Shepmaster's answer所解释的那样。
总结一下:在对象需要改变内部状态时,不要使用内部可变性,可以考虑采用一种模式,即将新状态上升为当前状态,并通过将 Arc 放入 RwLock 中,使旧状态的当前使用者继续持有它。
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
}
}
thread_local!
存储中使用RwLock
和Arc
等线程安全的数据结构是否有点毫无意义呢?在这里,非线程安全版本的RefCell
和Rc
是否足够呢? - undefined使用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(
// ...
);
}
#![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);
}
}
除了使用第三方的crate,另一种选择是将您的自定义类型(例如结构体)包装在std::cell::Cell
中,并放置在std::sync::Mutex
内。
Mutex
保护自定义类型实例,以便在多线程使用场景下进行并发访问Cell
提供内部可变性,因此从Mutex
获取锁的人可以修改自定义类型的内容。以下是代码示例:
use std::cell::Cell;
use std::sync::Mutex;
#[derive(Debug)]
struct Rectangle {
width :u16,
height:u16,
}
static GLOBAL_COUNTER_2: Mutex<Cell<Rectangle>> =
Mutex::new(Cell::new(
Rectangle{width:100u16, height:125u16}
));
fn global_var_demo()
{
if let Ok(mut currcell) = GLOBAL_COUNTER_2.lock() {
let mut value = currcell.get_mut();
value.width += 7;
value.height = value.height >> 1;
}
if let Ok(mut currcell) = GLOBAL_COUNTER_2.lock() {
// request the reference without moving the ownership
let value = currcell.get_mut();
assert_eq!(value.width, 107u16);
println!("new value in GLOBAL_COUNTER_2: {:?}", value);
}
}
Mutex
已经保证了独占访问并允许您修改其内部内容,Cell
是不必要的,这一点可以通过您使用的Cell::get_mut(&mut self)
来证明,它已经接受了一个可变引用。 - undefinedmutex.lock()
分配的mut value
应该足以修改内部实例。似乎有人因为答案不够精确而给我点了踩。:) - undefinedclass Manager
{
public:
static Manager* instance();
static void destroy();
}
use std::{mem};
struct Manager {
count: i32
}
static mut MANAGER_INSTANCE: Option<&'static mut Manager> = Option::None;
impl Drop for Manager {
fn drop(&mut self) {
println!("Dropping me!");
}
}
impl Manager {
pub fn instance() -> &'static mut Manager {
unsafe {
match MANAGER_INSTANCE {
Option::Some(ref mut manager) => *manager,
Option::None => {
println!("new instance!");
let manager_box = Box::new(Manager {count: 0});
let manager_raw = Box::into_raw(manager_box);
MANAGER_INSTANCE = Some(&mut *manager_raw);
&mut *manager_raw
}
}
}
}
pub fn destroy() {
unsafe {
if let Some(raw) = mem::replace(&mut MANAGER_INSTANCE, None) {
Box::from_raw(raw);
}
}
}
pub fn call_me(&mut self) {
self.count += 1;
println!("count: {}", self.count);
}
}
#[tokio::main]
pub async fn main() {
Manager::instance().call_me();
Manager::instance().call_me();
Manager::instance().call_me();
Manager::destroy();
Manager::instance().call_me();
Manager::instance().call_me();
Manager::instance().call_me();
Manager::destroy();
tokio::spawn(async {
Manager::destroy();
Manager::instance().call_me();
Manager::destroy();
}).await;
}
OnceCell<Mutex>
或者thread_local!(RefCell)
呢?你的代码既不安全又不可靠。 - undefined我的有限解决方案是定义一个结构体而不是全局可变的变量。为了使用该结构体,外部代码需要调用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();
...
}