मैं एक ग्लोबल, म्यूटेबल सिंगलटन कैसे बनाऊं?


140

सिस्टम में केवल एक पल के साथ एक संरचना बनाने और उपयोग करने का सबसे अच्छा तरीका क्या है? हां, यह आवश्यक है, यह OpenGL सबसिस्टम है, और इस की कई प्रतियाँ बनाना और इसे हर जगह पास करना भ्रम को दूर करेगा, बजाय इसे राहत देने के।

सिंगलटन को यथासंभव कुशल होने की आवश्यकता है। स्थैतिक क्षेत्र पर एक मनमानी वस्तु को संग्रहीत करना संभव नहीं लगता है, क्योंकि इसमें Vecएक विनाशकारी के साथ होता है । दूसरा विकल्प स्थिर क्षेत्र पर एक (असुरक्षित) पॉइंटर को स्टोर करना है, जो एक आवंटित आवंटित सिंगलटन को इंगित करता है। वाक्यविन्यास को ध्यान में रखते हुए, ऐसा करने का सबसे सुविधाजनक और सबसे सुरक्षित तरीका क्या है।


1
क्या आपने देखा है कि OpenGL के लिए मौजूदा जंग बाँध कैसे इसी समस्या को संभालते हैं?
शमपास्टर

20
हां, यह आवश्यक है, यह OpenGL सबसिस्टम है, और इस की कई प्रतियाँ बनाना और इसे हर जगह पास करना भ्रम को दूर करेगा, बजाय इसे राहत देने के। => यह आवश्यक की परिभाषा नहीं है , यह शायद सुविधाजनक है (पहली बार में) लेकिन आवश्यक नहीं है।
Matthieu M.

3
हां आपके पास एक बिंदु है। हालाँकि OpenGL एक बड़ी राज्य मशीन है, वैसे भी, मैं कुछ के करीब हूँ, कहीं भी इसका कोई क्लोन नहीं होगा, जिसके उपयोग से केवल OpenGL की त्रुटियां होंगी।
स्टीवनक्युकेरा

जवाबों:


198

गैर-जवाब जवाब

सामान्य रूप से वैश्विक स्थिति से बचें। इसके बजाय, ऑब्जेक्ट को कहीं जल्दी (शायद 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तत्कालीन हटा देते हैं तो आपके पास बिना किसी परिवर्तन के एक वैश्विक सिंगलटन है।

आप कई समवर्ती पाठकों को अनुमति देने के लिए एक के RwLockबजाय का उपयोग भी कर सकते हैं Mutex

एक बार_सेल का उपयोग करना

एक बार_सेल क्रेट मैन्युअल रूप से एक सिंगलटन बनाने के कुछ नशे से दूर ले जा सकता है। यहाँ एक वैश्विक परिवर्तनशील वेक्टर है:

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तत्कालीन हटा देते हैं तो आपके पास बिना किसी परिवर्तन के एक वैश्विक सिंगलटन है।

आप कई समवर्ती पाठकों को अनुमति देने के लिए एक के RwLockबजाय का उपयोग भी कर सकते हैं Mutex

एक विशेष मामला: एटमिक्स

यदि आपको केवल पूर्णांक मान को ट्रैक करने की आवश्यकता है, तो आप सीधे परमाणु का उपयोग कर सकते हैं :

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

मैनुअल, निर्भरता-मुक्त कार्यान्वयन

यह बहुत तेजी से आधुनिक जंग के लिए कुछ tweaks केstdin साथ रस्ट 1.0 के कार्यान्वयन से बहुत कम है । के आधुनिक कार्यान्वयन को भी देखना चाहिए 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

यह कोड रस्ट 1.42.0 के साथ संकलित है। Stdinआवंटित मेमोरी को मुक्त करने का प्रयास करने के लिए कुछ अस्थिर सुविधाओं का वास्तविक कार्यान्वयन , जो यह कोड नहीं करता है।

वास्तव में, आप शायद इसे SingletonReaderलागू करना चाहते हैं Derefऔर DerefMutइसलिए आपको ऑब्जेक्ट में झाँकने और खुद को लॉक करने की ज़रूरत नहीं है।

यह सब काम आपके लिए क्या आलसी-स्थिर या एक बार करना है।

"वैश्विक" का अर्थ

कृपया ध्यान दें कि आप अभी भी सामान्य जंग रस्सियों और मॉड्यूल-स्तर की गोपनीयता का उपयोग किसी चर staticया lazy_staticचर तक पहुंच को नियंत्रित करने के लिए कर सकते हैं। इसका मतलब है कि आप इसे किसी मॉड्यूल या किसी फ़ंक्शन के अंदर भी घोषित कर सकते हैं और यह उस मॉड्यूल / फ़ंक्शन के बाहर पहुंच योग्य नहीं होगा। पहुँच को नियंत्रित करने के लिए यह अच्छा है:

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

हालाँकि, चर अभी भी वैश्विक है कि इसका एक उदाहरण है जो पूरे कार्यक्रम में मौजूद है।


72
बहुत सोच-विचार के बाद मैं सिंगलटन का उपयोग नहीं करने के लिए आश्वस्त हूं, और इसके बजाय किसी भी वैश्विक चर का उपयोग नहीं करता हूं और सब कुछ पास करता हूं। कोड को अधिक स्व-दस्तावेजीकरण बनाता है क्योंकि यह स्पष्ट है कि रेंडरर किस फ़ंक्शन का उपयोग करता है। अगर मैं वापस सिंगलटन में बदलना चाहता हूं, तो ऐसा करना आसान होगा।
स्टीवनकुकेरा

4
उत्तर के लिए धन्यवाद, इसने बहुत मदद की। मैंने अभी सोचा कि मैं यहाँ एक टिप्पणी का वर्णन करने के लिए कहूँगा जो मुझे lazy_static के वैध उपयोग के मामले के रूप में दिखाई दे रही है! मैं इसे एक C अनुप्रयोग में इंटरफ़ेस के लिए उपयोग कर रहा हूं जो लोडिंग / अनलोडिंग मॉड्यूल (साझा ऑब्जेक्ट) और जंग कोड को इन मॉड्यूलों में से एक है। मुझे लोड पर एक वैश्विक का उपयोग करने की तुलना में बहुत अधिक विकल्प नहीं दिखता है क्योंकि मेरे पास मुख्य () पर कोई नियंत्रण नहीं है और मेरे मॉड्यूल के साथ कोर एप्लिकेशन कैसे इंटरफेस करता है। मुझे मूल रूप से उन चीजों की एक वेक्टर की आवश्यकता थी जो मेरे मॉड लोड होने के बाद रनटाइम पर जोड़ी जा सकती हैं।
मोइसेस सिल्वा

1
@MoisesSilva हमेशा एक सिंगलटन की आवश्यकता के लिए कुछ कारण होने जा रहा है , लेकिन इसका उपयोग करने वाले कई मामलों में इसका उपयोग करना अनावश्यक है। आपके कोड को जाने बिना, यह संभव है कि सी एप्लिकेशन को प्रत्येक मॉड्यूल को एक "उपयोगकर्ता डेटा" वापस करने की अनुमति देनी चाहिए void *जो बाद में प्रत्येक मॉड्यूल के तरीकों में वापस पारित हो जाता है। यह C कोड के लिए एक विशिष्ट एक्सटेंशन पैटर्न है। यदि आवेदन इसके लिए अनुमति नहीं देता है और आप इसे बदल नहीं सकते हैं, तो हाँ, एक सिंगलटन एक अच्छा समाधान हो सकता है।
Shepmaster

3
@Orik क्या आप यह बताना चाहेंगे कि क्यों? मैं लोगों को कुछ ऐसा करने से हतोत्साहित करता हूं जो अधिकांश भाषाओं में एक खराब विचार है (यहां तक ​​कि ओपी ने भी माना कि एक वैश्विक उनके आवेदन के लिए एक बुरा विकल्प था)। सामान्य तौर पर यही होता है। फिर मैं इसे कैसे करना है इसके लिए दो समाधान दिखाता हूं। मैंने सिर्फ lazy_static1.24.1 जंग में उदाहरण का परीक्षण किया और यह ठीक काम करता है। यहां external staticकहीं नहीं है। शायद आपको यह सुनिश्चित करने की आवश्यकता है कि आपने जवाब को पूरी तरह से समझ लिया है।
शमपास्टर

1
@ अगर आपको टोकरे का उपयोग करने की मूल बातें के साथ मदद की ज़रूरत है, तो मैं आपको सुझाव देता हूं कि द रस्ट प्रोग्रामिंग लैंग्वेज को फिर से पढ़ें । एक अनुमान लगाने का खेल बनाने का अध्याय निर्भर करता है कि निर्भरता को कैसे जोड़ा जाए।
शेमास्टर

0

वैश्विक पहुंच के लिए स्पिनलॉक का उपयोग करें ।

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

यदि आप उत्परिवर्तित अवस्था (सिंग्लटन नहीं) चाहते हैं, तो अधिक विवरणों के लिए देखें क्या नहीं करना है

आशा है कि यह उपयोगी है।


-1

मेरे अपने डुप्लीकेट प्रश्न का उत्तर देना ।

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

संपादित करें:

इसे एक बार_से सुलझाने के लिए प्रबंधित किया गया, जिसे मैक्रो की आवश्यकता नहीं है।

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
यह उत्तर मौजूदा उत्तरों की तुलना में कुछ नया प्रदान नहीं करता है, जो पहले से ही चर्चा करते हैं lazy_staticऔर नए हैं once_cell। SO पर डुप्लिकेट के रूप में चीजों को चिह्नित करने का बिंदु अनावश्यक जानकारी होने से बचना है।
शेमास्टर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.