< 返回版块

lithbitren 发表于 2020-12-07 23:05

Tags:全局变量,lazy_static

lazy_static!要不就是静态对象引用,要不就得包个Mutex,包了Mutex后则必须lock().unwrap()才能使用,对于串行单线程的程序有没有更方便的方法去使用可变的全局变量呢?其他语言就算是单线程同步代码,全局变量加锁再操作也是有很大性能影响的,所以不知道rust里这个锁会不会有啥性能影响,但无论是各类中英文教程还是StackOverflow的讨论,好像都只提到了上述这一种单例方法,最多是多线程的时候再用Arc包一下。

评论区

写评论
Bai-Jinlin 2023-11-26 20:41

有一说一,Linux上OnceCell配个锁,性能损耗基本上可以忽略不记,Linux上的futex实现的Mutex会自旋一段时间,要是你真没并行是可以立刻获取到锁的 不用任何系统调用。

除此之外要是你真能保证正确使用就直接static mut,然后unsafe,不会有任何性能消耗,和c的全局变量一样。

ribs 2023-11-25 14:34

单线程下,我觉得完全可以unsafe去用可变静态变量

static mut NAME: &str = "a";

fn main() {
    println!("{}", unsafe { NAME } );
    unsafe { NAME = "b" }
    println!("{}", unsafe { NAME } );
}
GUO 2023-11-24 10:15

thread_local 的策略是每个线程各自创建一个同名变量,如果多线程的话,数据是不好线程间共享的。 这种玩法仅限于多线程间不需要共享数据的场景。

--
👇
lithbitren: 谢谢分享,果然可以用,性能直逼原生数组,如果给原生数组带套Refcell的话,差距基本忽略不计了。

不过不是很熟这个宏,api文档还没细看,试了下得用clone才能把对象取出来,感觉方法不太对路。

#![allow(unused)]
use std::sync::Mutex;
use std::rc::Rc;
use std::cell::RefCell;
use std::time::Instant;

#[macro_use]
extern crate lazy_static;
extern crate thread_local;

lazy_static! {
    static ref MUTEX_ARR: Mutex<Vec<i32>> = Mutex::new(vec![]);
}

fn lazy_static_push(i: i32) {
    MUTEX_ARR.lock().unwrap().push(i);
}

thread_local! {
    // static REFCELL_ARR: Rc<RefCell<Vec<i32>>> = Rc::new(RefCell::new(vec![]));
    static REFCELL_ARR: RefCell<Vec<i32>> = RefCell::new(vec![]);
}

fn thread_local_push(i: i32) {
    REFCELL_ARR.with(|arr| arr.borrow_mut().push(i));
}

fn native_push(arr: &mut Vec<i32>, i: i32) {
    arr.push(i);
}

fn main() {

    let n = 10000000;
    println!("n: {}", n);
    
    let t = Instant::now();
    for i in 0..n {
        lazy_static_push(i);
    }
    println!("lazy_static! time: {:?}", t.elapsed());
    // println!("{:?}", ARR.lock().unwrap());
    
    let t = Instant::now();
    for i in 0..n {
        thread_local_push(i);
    }
    println!("thread_local! time: {:?}", t.elapsed());
    // println!("{:?}", ARR.with(|arr| arr.borrow().clone()));
    
    let mut arr = vec![];
    
    let t = Instant::now();
    for i in 0..n {
        native_push(&mut arr, i);
    }
    println!("native push! time: {:?}", t.elapsed());
    // println!("{:?}", arr);
    
}
n: 10000000
lazy_static! time: 278.030881ms
thread_local! time: 66.285571ms
native push time: 48.354768ms

--
👇
fakeshadow: thread_local!

作者 lithbitren 2020-12-09 18:56

学习了,其实只是想找到简洁易上手且不太影响性能的全局变量方法,就像其他语言那样,其实下面推荐的thread_local相对来说已经够好用了。

--
👇
aariety: 你如果只是想要一个现成的 crate,可以另找一个库,就像下面推荐的那样。

不过你的标题问的是怎么实现;如果你想自己实现的话,参考一下 laze_static! 这个宏的源码就可以了,基本原理很容易理解,就是懒加载:

// 展开 lazy_static!{ static ref THE_VALUE: THE_TYPE = ... } 大概是下面这样
pub struct THE_VALUE { __private_field: () }

impl Deref for THE_VALUE {
    type Target = THE_TYPE;
    fn deref(&self) -> &THE_TYPE {
        // 懒加载就是在这里实现的
        // 仅当第一次解引用时进行初始化。之后就直接使用初始化后的值
    }
}

static THE_VALUE: THE_VALUE = THE_VALUE { __private_field: () };

非关键内容都省去了,总之原理就是这么个原理。

你还可以看看标准库里的 std::lazy 这个模块。特别是 LazySyncLazy。这两个东西就是标准库里可以实现同样目的的结构体,线程安全和不安全都有。遗憾的是,它们现在还只能在 nightly 版本里使用。

aariety 2020-12-09 18:26

你如果只是想要一个现成的 crate,可以另找一个库,就像下面推荐的那样。

不过你的标题问的是怎么实现;如果你想自己实现的话,参考一下 laze_static! 这个宏的源码就可以了,基本原理很容易理解,就是懒加载:

// 展开 lazy_static!{ static ref THE_VALUE: THE_TYPE = ... } 大概是下面这样
pub struct THE_VALUE { __private_field: () }

impl Deref for THE_VALUE {
    type Target = THE_TYPE;
    fn deref(&self) -> &THE_TYPE {
        // 懒加载就是在这里实现的
        // 仅当第一次解引用时进行初始化。之后就直接使用初始化后的值
    }
}

static THE_VALUE: THE_VALUE = THE_VALUE { __private_field: () };

非关键内容都省去了,总之原理就是这么个原理。

你还可以看看标准库里的 std::lazy 这个模块。特别是 LazySyncLazy。这两个东西就是标准库里可以实现同样目的的结构体,线程安全和不安全都有。遗憾的是,它们现在还只能在 nightly 版本里使用。

作者 lithbitren 2020-12-08 13:34

如果用unsafe可以实现不带套的全局变量吗?

作者 lithbitren 2020-12-08 01:56

谢谢分享,果然可以用,性能直逼原生数组,如果给原生数组带套Refcell的话,差距基本忽略不计了。

不过不是很熟这个宏,api文档还没细看,试了下得用clone才能把对象取出来,感觉方法不太对路。

#![allow(unused)]
use std::sync::Mutex;
use std::rc::Rc;
use std::cell::RefCell;
use std::time::Instant;

#[macro_use]
extern crate lazy_static;
extern crate thread_local;

lazy_static! {
    static ref MUTEX_ARR: Mutex<Vec<i32>> = Mutex::new(vec![]);
}

fn lazy_static_push(i: i32) {
    MUTEX_ARR.lock().unwrap().push(i);
}

thread_local! {
    // static REFCELL_ARR: Rc<RefCell<Vec<i32>>> = Rc::new(RefCell::new(vec![]));
    static REFCELL_ARR: RefCell<Vec<i32>> = RefCell::new(vec![]);
}

fn thread_local_push(i: i32) {
    REFCELL_ARR.with(|arr| arr.borrow_mut().push(i));
}

fn native_push(arr: &mut Vec<i32>, i: i32) {
    arr.push(i);
}

fn main() {

    let n = 10000000;
    println!("n: {}", n);
    
    let t = Instant::now();
    for i in 0..n {
        lazy_static_push(i);
    }
    println!("lazy_static! time: {:?}", t.elapsed());
    // println!("{:?}", ARR.lock().unwrap());
    
    let t = Instant::now();
    for i in 0..n {
        thread_local_push(i);
    }
    println!("thread_local! time: {:?}", t.elapsed());
    // println!("{:?}", ARR.with(|arr| arr.borrow().clone()));
    
    let mut arr = vec![];
    
    let t = Instant::now();
    for i in 0..n {
        native_push(&mut arr, i);
    }
    println!("native push! time: {:?}", t.elapsed());
    // println!("{:?}", arr);
    
}
n: 10000000
lazy_static! time: 278.030881ms
thread_local! time: 66.285571ms
native push time: 48.354768ms

--
👇
fakeshadow: thread_local!

fakeshadow 2020-12-08 00:49

thread_local!

作者 lithbitren 2020-12-08 00:32

嗯?lazy_static!不是要求要实现Sync才能用吗,求大佬明示。

#![allow(unused)]
// use std::sync::Mutex;
use std::rc::Rc;
use std::cell::RefCell;

#[macro_use]
extern crate lazy_static;

lazy_static! {
    // static ref ARR: Mutex<Vec<i32>> = Mutex::new(vec![]);
    static ref ARR: Rc<RefCell<Vec<i32>>> = Rc::new(RefCell::new(vec![]));
}

fn arr_push(i: i32) {
    // ARR.lock().unwrap().push(i);
    ARR.borrow_mut().push(i);
}

fn main() {
    
    arr_push(1);
    arr_push(2);
    arr_push(3);

    // println!("{:?}", ARR.lock().unwrap());
    println!("{:?}", ARR.borrow());
}
error[E0277]: `Rc<RefCell<Vec<i32>>>` cannot be shared between threads safely
  --> src/main.rs:9:1
   |
9  | / lazy_static! {
10 | |     // static ref ARR: Mutex<Vec<i32>> = Mutex::new(vec![]);
11 | |     static ref ARR: Rc<RefCell<Vec<i32>>> = Rc::new(RefCell::new(vec![]));
12 | | }
   | |_^ `Rc<RefCell<Vec<i32>>>` cannot be shared between threads safely
   | 
  ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 |   pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                      ---- required by this bound in `lazy_static::lazy::Lazy`
   |
   = help: the trait `Sync` is not implemented for `Rc<RefCell<Vec<i32>>>`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

--
👇
gwy15: Rc<RefCell<>>

gwy15 2020-12-07 23:28

Rc<RefCell<>>

1 共 10 条评论, 1 页