Rust 全局变量的最佳实践 lazy_static/OnceLock/Mutex/RwLock
在实际项目开发中,难免需要用到全局变量,比如全局配置信息,全局内存池等,此类数据结构可能在多处需要被使用,保存为全局变量可以很方便的进行修改与读取。
在Rust中,如果只是读取静态变量是比较简单的,比如全局变量是一个usize
或者& str
等类型的值。如果全局变量是需要初始化产生的就比较复杂了,比如解析一个配置文件,然后把配置文件中的内容赋给全局变量。由于全局变量要被修改,这个全局变量得是可变的,也就是说产生了全局可变变量,而这种方式违反了Rust的设计原则。
一般方法
struct Config {
id: u64,
}
impl Config {
fn new() -> Config {
Config {
id: 0,
}
}
}
lazy_static::lazy_static! {
static ref CACHE: Mutex<Config> = Mutex::new(Config::new());
}
fn func1() {
CACHE.lock().unwrap().id = 1;
}
fn func2() {
CACHE.lock().unwrap().id = 2;
}
fn func3() -> u64 {
return CACHE.lock().unwrap().id;
}
fn main() {
func1();
let id1 = func3();
println!("id1 = {}", id1);
func2();
let id2 = func3();
println!("id2 = {}", id2);
}
这种方法一般可以满足需求,但是需要一开始就将变量初始化。
如果有未初始化的字段,可以使用Option
等类型搞定。
更好的办法
use std::{
sync::{OnceLock, RwLock},
thread,
time::Duration,
};
struct AppData {
count: i64,
name: String,
ptr: usize,
}
static APP_SHARE: OnceLock<RwLock<AppData>> = OnceLock::new();
fn th_loop1() {
let app = APP_SHARE.get().unwrap();
loop {
{
let app2 = app.read().unwrap();
println!("th 1 read: {}", app2.count);
if (app2.count > 60) {
break;
}
}
thread::sleep(Duration::new(1, 0));
}
println!("th 1 end!!");
}
fn th_loop2() {
let app = APP_SHARE.get().unwrap();
loop {
{
let mut app2 = app.write().unwrap();
app2.count += 1;
println!("th 2 write: {}", app2.count);
if (app2.count > 80) {
break;
}
}
thread::sleep(Duration::new(1, 0));
}
println!("th 2 end!!");
}
fn main() {
// 在这里初始化
let app = APP_SHARE.get_or_init(|| {
RwLock::new(AppData {
count: 12 * 4 + 5,
name: "abc".to_string(),
ptr: (0xFF002403) as usize,
})
});
{
let app2 = app.read().unwrap();
println!("init ok: {} {} {}", app2.count, app2.name, app2.ptr);
}
// 线程里面使用
let t1 = thread::spawn(th_loop1);
let t2 = thread::spawn(th_loop2);
t1.join().unwrap();
t2.join().unwrap();
println!("===============");
}
这里的 AppData 一开始没有初始化,在程序运行时才进行。
能更好适应一般的全局变量需求!!
读多写少的情况,也可以这样:
use std::{
cell::{Cell, RefCell},
default,
sync::{Arc, OnceLock, RwLock},
thread,
time::Duration,
};
struct T1 {
a: i64,
b: i64,
}
struct AppData {
count: i64,
name: String,
ptr: usize,
count2: RwLock<T1>,
}
static APP_SHARE: OnceLock<AppData> = OnceLock::new();
fn th_loop1() {
let app = APP_SHARE.get().unwrap();
loop {
{
let c2 = app.count2.read().unwrap();
println!("th 1 read: {} {}", app.count, c2.a);
if (app.count > 60 || c2.a > 10) {
break;
}
}
thread::sleep(Duration::new(1, 0));
}
println!("th 1 end!!");
}
fn th_loop2() {
let app = APP_SHARE.get().unwrap();
loop {
{
let mut c2 = app.count2.write().unwrap();
c2.a += 1;
println!("th 2 write: {} {}", app.count, c2.a);
if (app.count > 80 || c2.a > 20) {
break;
}
}
thread::sleep(Duration::new(1, 0));
}
println!("th 2 end!!");
}
fn main() {
// 在这里初始化
let app = APP_SHARE.get_or_init(|| AppData {
count: 12 * 4 + 5,
name: "abc".to_string(),
ptr: (0xFF002403) as usize,
count2: RwLock::new(T1 { a: 1, b: 2 }),
});
{
println!("init ok: {} {} {}", app.count, app.name, app.ptr);
}
// 线程里面使用
let t1 = thread::spawn(th_loop1);
let t2 = thread::spawn(th_loop2);
t1.join().unwrap();
t2.join().unwrap();
println!("===============");
}