Rust में कंकरेंसी और मल्टीथ्रेडिंग: एडवांस्ड लेवल
\n\nकंकरेंसी और मल्टीथ्रेडिंग सिस्टम प्रोग्रामिंग के सबसे शक्तिशाली और साथ ही जटिल पहलुओं में से एक हैं। Rust एक अनोखा दृष्टिकोण प्रस्तुत करता है: कंपाइल टाइम पर मेमोरी सुरक्षा की गारंटी, जो डेटा रेस (data race) और डैंगलिंग पॉइंटर्स को खत्म करता है। इस लेख में, हम Rust में कंकरेंसी के एडवांस्ड तकनीकों को समझेंगे, जिसमें async/await, Tokio लाइब्रेरी, Send और Sync ट्रेट्स, और लो-लेवल सिंक्रोनाइज़ेशन प्रिमिटिव शामिल हैं।
\n\nयह सामग्री उन डेवलपर्स के लिए है जो Rust की बुनियादी बातों से परिचित हैं और हाई-परफॉर्मेंस पैरेलल कंप्यूटिंग में गहराई से जाना चाहते हैं।
\n\n1. कंकरेंसी मॉडल: OS थ्रेड्स और एसिंक्रोनसी
\n\nRust दो मुख्य मॉडल सपोर्ट करता है: ऑपरेटिंग सिस्टम थ्रेड्स (OS threads) और एसिंक्रोनस टास्क (async tasks)। इनके बीच चुनाव सीनारियो पर निर्भर करता है: CPU-इंटेंसिव कार्यों के लिए थ्रेड्स बेहतर हैं, I/O-bound के लिए async।
\n\nOS थ्रेड्स (std::thread)
\n\nRust में थ्रेड बनाना आसान है, लेकिन डेटा के स्वामित्व पर ध्यान देने की आवश्यकता है:
\n\nuse std::thread;\n\nlet data = vec![1, 2, 3];\n\nlet handle = thread::spawn(move || {\n println!("{:?}", data);\n});\n\nhandle.join().unwrap();\n\nमुख्य बिंदु move कीवर्ड है। यह क्लोज़र को स्वामित्व हस्तांतरित करता है। अगर डेटा को थ्रेड्स के बीच साझा करना है, तो Arc<Mutex<T>> का उपयोग करें।
एसिंक्रोनस प्रोग्रामिंग (async/await)
\n\nएसिंक्रोनस कोड एक ही थ्रेड के भीतर काम करता है, I/O प्रतीक्षा के दौरान टास्क के बीच स्विच करता है। यह कॉन्टेक्स्ट स्विचिंग के ओवरहेड को कम करता है। एक सरल एसिंक्रोनस फंक्शन का उदाहरण:
\n\nuse tokio::time::{sleep, Duration};\n\n#[tokio::main]\nasync fn main() {\n let task1 = async_task("कार्य 1");\n let task2 = async_task("कार्य 2");\n\n tokio::join!(task1, task2);\n}\n\nasync fn async_task(name: &str) {\n println!("{} शुरू हुआ", name);\n sleep(Duration::from_secs(1)).await;\n println!("{} पूरा हुआ", name);\n}\n\ntokio लाइब्रेरी एसिंक्रोनस Rust के लिए मानक है। यह एक मल्टीथ्रेडेड वर्क-स्टीलिंग शेड्यूलर प्रदान करती है जो CPU कोर पर कार्यों को कुशलतापूर्वक वितरित करता है।
2. Send और Sync ट्रेट्स: कंकरेंसी सुरक्षा
\n\nदो मुख्य ट्रेट्स Rust में कंकरेंसी सुरक्षा सुनिश्चित करते हैं:
\n\n- \n
- Send — टाइप को थ्रेड्स के बीच सुरक्षित रूप से स्थानांतरित किया जा सकता है। \n
- Sync — टाइप को एक साथ कई थ्रेड्स से सुरक्षित रूप से संदर्भित किया जा सकता है। \n
अधिकांश Rust प्रिमिटिव स्वचालित रूप से इन ट्रेट्स को लागू करते हैं यदि उनके घटक उन्हें लागू करते हैं। उदाहरण के लिए, Arc<Mutex<i32>> Send और Sync है, जबकि Rc<RefCell<i32>> नहीं है।
एक उदाहरण देखें जो Send के उल्लंघन के कारण कंपाइल नहीं होगा:
\n\nuse std::rc::Rc;\nuse std::thread;\n\nlet data = Rc::new(5);\n\nthread::spawn(move || {\n println!("{}", data);\n}); // त्रुटि: Rc<i32> Send को लागू नहीं करता\n\nRust कंपाइलर एक स्पष्ट त्रुटि संदेश देगा, जो कंपाइल टाइम पर डेटा रेस को रोकता है। यह भाषा के मुख्य लाभों में से एक है।
\n\n3. एडवांस्ड सिंक्रोनाइज़ेशन: चैनल, म्यूटेक्स और RwLock
\n\nथ्रेड्स के बीच डेटा साझा करने के लिए Rust कई प्रिमिटिव प्रदान करता है। आइए सबसे शक्तिशाली पर नज़र डालें।
\n\nचैनल (mpsc)
\n\nमल्टीथ्रेडेड चैनल (multi-producer, single-consumer) कई थ्रेड्स से एक रिसीवर को संदेश भेजने की अनुमति देते हैं:
\n\nuse std::sync::mpsc;\nuse std::thread;\n\nlet (tx, rx) = mpsc::channel();\n\nfor i in 0..3 {\n let tx_clone = tx.clone();\n thread::spawn(move || {\n tx_clone.send(i).unwrap();\n });\n}\n\ndrop(tx); // ट्रांसमीटर बंद करें\n\nfor received in rx {\n println!("प्राप्त: {}", received);\n}\n\nचैनल "प्रोड्यूसर-कंज्यूमर" आर्किटेक्चर के लिए आदर्श हैं।