[rust] 전역적이고 가변적 인 싱글 톤은 어떻게 만듭니 까?

시스템에서 인스턴스화가 하나 뿐인 구조체를 만들고 사용하는 가장 좋은 방법은 무엇입니까? 예, 이것이 필요합니다. 이것은 OpenGL 하위 시스템이며, 여러 복사본을 만들어 모든 곳에 전달하면 혼란을 덜어주기보다는 혼란을 가중시킬 수 있습니다.

싱글 톤은 가능한 한 효율적이어야합니다. Vec소멸자 가 포함되어 있기 때문에 정적 영역에 임의의 개체를 저장할 수 없습니다 . 두 번째 옵션은 힙 할당 싱글 톤을 가리키는 정적 영역에 (안전하지 않은) 포인터를 저장하는 것입니다. 구문을 간결하게 유지하면서이를 수행하는 가장 편리하고 안전한 방법은 무엇입니까?



답변

무응답 답변

일반적으로 글로벌 상태를 피하십시오. 대신 어딘가에 (아마도에서 main) 객체를 생성 한 다음 해당 객체에 대한 변경 가능한 참조를 필요한 위치로 전달하십시오. 이것은 일반적으로 코드를 추론하기 쉽게 만들고 거꾸로 구부릴 필요가 없습니다.

전역 가변 변수를 원한다고 결정하기 전에 거울을 자세히 살펴보십시오. 유용한 경우가 드물기 때문에 방법을 아는 것이 좋습니다.

아직도 하나 만들고 싶어 …?

게으른 정적 사용

게으른 정적의 상자 수동으로 싱글을 만드는 천역의 일부를 멀리 걸릴 수 있습니다. 다음은 전역 가변 벡터입니다.

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

를 제거하면 Mutex가변성이없는 전역 싱글 톤이 있습니다.

a RwLock대신 a Mutex를 사용하여 여러 동시 판독기를 허용 할 수도 있습니다 .

once_cell 사용

once_cell 상자 수동으로 싱글을 만드는 천역의 일부를 멀리 걸릴 수 있습니다. 다음은 전역 가변 벡터입니다.

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

를 제거하면 Mutex가변성이없는 전역 싱글 톤이 있습니다.

a RwLock대신 a Mutex를 사용하여 여러 동시 판독기를 허용 할 수도 있습니다 .

특별한 경우 : 원 자학

정수 값만 추적해야하는 경우 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));
}

수동, 종속성없는 구현

이것은 Rust 1.0 구현stdin 에서 큰 영향을 받았습니다 . 또한 io::Lazy. 나는 각 라인이하는 일에 대해 인라인으로 주석을 달았습니다.

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일부 불안정한 기능을 사용하여 할당 된 메모리를 해제하려고 시도하지만이 코드는 그렇지 않습니다.

정말로, 당신은 아마도 SingletonReader구현 Deref하고 싶을 DerefMut것이므로 객체를 찔러서 직접 잠글 필요가 없습니다.

이 모든 작업은 lazy-static 또는 once_cell이 수행하는 작업입니다.

“글로벌”의 의미

static또는 lazy_static변수에 대한 액세스를 제어하기 위해 일반 Rust 범위 지정 및 모듈 수준 개인 정보를 계속 사용할 수 있습니다 . 즉, 모듈 또는 함수 내부에서 선언 할 수 있으며 해당 모듈 / 함수 외부에서 액세스 할 수 없습니다. 액세스 제어에 유용합니다.

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

그러나 변수는 전체 프로그램에 걸쳐 존재하는 하나의 인스턴스가 있다는 점에서 여전히 전역 적입니다.


답변

전역 액세스를 위해 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(
        // ...
    );
}

변경 가능한 상태 (NOT Singleton)를 원한다면 Rust에서하지 말아야 할 일을 참조하세요 .

도움이 되었기를 바랍니다.


답변

내 자신의 중복 질문에 대답 .

Cargo.toml :

[dependencies]
lazy_static = "1.4.0"

상자 루트 (lib.rs) :

#[macro_use]
extern crate lazy_static;

초기화 (안전하지 않은 블록 필요 없음) :

/// 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
    };
    ...

편집하다:

매크로가 필요없는 once_cell로 해결했습니다.

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


답변