Nasıl global, değişken bir singleton yaratırım?


141

Sistemde tek bir somutlama ile bir yapı oluşturmanın ve kullanmanın en iyi yolu nedir? Evet, bu gerekli, OpenGL alt sistemidir ve bunun birden çok kopyasını alıp her yere dağıtmak, onu rahatlatmak yerine kafa karışıklığı yaratacaktır.

Tekil mümkün olduğunca verimli olmalıdır. VecBir yıkıcı içerdiğinden, rastgele bir nesneyi statik alanda depolamak mümkün görünmemektedir . İkinci seçenek, bir (güvenli olmayan) göstericiyi statik alanda depolamaktır ve ayrılmış bir tekil öbeğe işaret eder. Sözdizimini kısa tutarken bunu yapmanın en uygun ve en güvenli yolu nedir?


1
OpenGL için mevcut Rust bağlamalarının aynı sorunu nasıl ele aldığına baktınız mı?
Shepmaster

20
Evet, bu gerekli, OpenGL alt sistemidir ve bunun birden çok kopyasını alıp her yere dağıtmak, onu rahatlatmak yerine kafa karışıklığı yaratacaktır. => Bu tanım değildir gerekli belki, uygun (ilk), ancak gerekli değildir.
Matthieu M.

3
Evet haklısın. OpenGL zaten büyük bir durum makinesi olduğu için, onun herhangi bir yerde bir klonu olmayacağından eminim ki, kullanımı yalnızca OpenGL hatalarına yol açar.
stevenkucera

Yanıtlar:


199

Cevapsız cevap

Genel olarak küresel durumdan kaçının. Bunun yerine, nesneyi erken bir yerde (belki de içinde main) inşa edin , ardından bu nesneye, ihtiyaç duyulan yerlere değişken referanslar aktarın. Bu genellikle kodunuzun akıl yürütmesini kolaylaştırır ve geriye doğru çok fazla eğilme gerektirmez.

Global değişken değişkenler istediğinize karar vermeden önce aynada kendinize iyice bakın. Yararlı olduğu nadir durumlar vardır, bu yüzden nasıl yapılacağını bilmeye değer.

Hala bir tane yapmak istiyor musun?

Tembel-statik kullanma

Tembel-statik sandık elle a tek yaratma ağır çalışma uzak alabilir. İşte küresel değişken bir vektör:

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

Eğer kaldırırsanız, Mutexo zaman herhangi bir değişkenlik olmadan global bir singletonunuz olur.

Birden çok eşzamanlı okuyucuya izin vermek için a RwLockyerine a da kullanabilirsiniz Mutex.

Once_cell kullanma

Once_cell sandık elle a tek yaratma ağır çalışma uzak alabilir. İşte küresel değişken bir vektör:

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

Eğer kaldırırsanız, Mutexo zaman herhangi bir değişkenlik olmadan global bir singletonunuz olur.

Birden çok eşzamanlı okuyucuya izin vermek için a RwLockyerine a da kullanabilirsiniz Mutex.

Özel bir durum: atomlar

Yalnızca bir tamsayı değerini izlemeniz gerekiyorsa, doğrudan bir atom kullanabilirsiniz :

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

Manuel, bağımlılık içermeyen uygulama

Bu, modern Rust için bazı ince ayarlarla birlikte Rust 1.0 uygulamasındanstdin büyük ölçüde kaynaklanıyor . Ayrıca, modern uygulamasına da bakmalısınız io::Lazy. Her satırın ne yaptığını satır içi olarak yorumladım.

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

Bu çıktı:

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

Bu kod Rust 1.42.0 ile derlenmiştir. Gerçek uygulamalar, Stdinbu kodun yapmadığı, ayrılmış belleği serbest bırakmaya çalışmak için bazı kararsız özellikleri kullanır.

Gerçekten, muhtemelen bir SingletonReaderalet yapmak istersiniz Derefve DerefMutbu yüzden nesneyi dürtmek ve kendiniz kilitlemek zorunda kalmazsınız.

Tüm bu işler tembel-statik veya once_cell'in sizin için yaptığı şeydir.

"Küresel" kelimesinin anlamı

Bir staticveya lazy_staticdeğişkene erişimi kontrol etmek için normal Rust kapsamını ve modül düzeyinde gizliliği kullanmaya devam edebileceğinizi lütfen unutmayın . Bu, onu bir modülde veya hatta bir işlevin içinde bildirebileceğiniz ve bu modül / işlevin dışında erişilemeyeceği anlamına gelir. Bu, erişimi kontrol etmek için iyidir:

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

Bununla birlikte, değişken, programın tamamında var olan bir örneği olması nedeniyle hala globaldir.


72
Çok düşündükten sonra, Singleton'ı kullanmamaya ve bunun yerine hiçbir global değişken kullanmamaya ve her şeyi aktarmaya ikna oldum. Oluşturucuya hangi işlevlerin eriştiği açık olduğundan, kodu daha fazla kendi kendini belgeleyen hale getirir. Tekrar singleton'a geçmek istersem, bunu yapmak diğer yoldan daha kolay olacaktır.
stevenkucera

4
Cevap için teşekkürler, çok yardımcı oldu. Lazy_static! İçin geçerli bir kullanım durumu olarak gördüğüm şeyi açıklamak için buraya bir yorum bırakacağımı düşündüm. Modüllerin (paylaşılan nesneler) yüklenmesine / boşaltılmasına izin veren bir C uygulamasına arayüz oluşturmak için kullanıyorum ve pas kodu bu modüllerden biridir. Yüklü bir global kullanmaktan daha fazla seçenek görmüyorum çünkü main () üzerinde hiçbir kontrolüm yok ve çekirdek uygulama modülümle nasıl arayüz oluşturuyor. Modum yüklendikten sonra çalışma zamanında eklenebilecek şeylerin bir vektörüne ihtiyacım vardı.
Moises Silva

1
@MoisesSilva her zaman olacak bazı singleton'ununu ihtiyacımız nedeni, ama buna kullanıldığı vakaların çoğunda kullanmak gereksizdir. Kodunuzu bilmeden, C uygulamasının her modülün bir "kullanıcı verisi" döndürmesine izin vermesi mümkündür, void *bu daha sonra her modülün yöntemlerine geri aktarılır. Bu, C kodu için tipik bir uzatma modelidir. Uygulama buna izin vermiyorsa ve onu değiştiremiyorsanız, evet, tek bir çözüm iyi bir çözüm olabilir.
Shepmaster

3
@Worik nedenini açıklar mısın? İnsanları çoğu dilde kötü bir fikir olan bir şey yapmaktan caydırıyorum (OP bile küresel bir uygulamanın kendi uygulamaları için kötü bir seçim olduğunu kabul etti). Bu neyin genelde vasıtası. Daha sonra nasıl yapılacağına dair iki çözüm gösteriyorum. lazy_staticÖrneği Rust 1.24.1'de test ettim ve tam olarak çalışıyor. Burada hiçbir yer yok external static. Belki yanıtı tam olarak anladığınızdan emin olmak için kendi başınıza bir şeyler kontrol etmeniz gerekiyor.
Shepmaster

1
@Worik, bir sandık nasıl kullanılacağı konusunda yardıma ihtiyacınız varsa, Rust Programlama Dilini yeniden okumanızı öneririm . Bir tahmin oyunu oluşturma konusunda bölüm nasıl bağımlılıkları eklemek için gösteriler.
Shepmaster

0

Küresel erişim için SpinLock kullanın .

#[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(
        // ...
    );
}

Değişebilir durum (Singleton DEĞİL) istiyorsanız, daha fazla açıklama için Rust'ta Ne Yapılmaması Gerekir konusuna bakın .

Umarım yardımcı olur.


-1

Yinelenen sorumu cevaplıyorum .

Cargo.toml:

[dependencies]
lazy_static = "1.4.0"

Kasa kökü (lib.rs):

#[macro_use]
extern crate lazy_static;

Başlatma (güvenli olmayan engellemeye gerek yok):

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

DÜZENLE:

Makroya ihtiyaç duymayan once_cell ile çözmeyi başardı.

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

2
Bu cevap, halihazırda tartışılan lazy_staticve daha yeni olan mevcut cevaplara kıyasla yeni bir şey sağlamaz once_cell. Nesneleri SO'da kopya olarak işaretlemenin amacı, fazlalık bilgilerden kaçınmaktır.
Shepmaster
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.