Rust में समवर्तीता और मल्टीथ्रेडिंग: सुरक्षित समानांतर कोड लिखना
Rust उन कुछ भाषाओं में से एक है जो कंपाइल समय पर डेटा रेस (data races) की समस्या को हल करता है। स्वामित्व (ownership) और उधार (borrowing) प्रणाली के कारण, Rust में समवर्ती कोड न केवल तेज़ होता है, बल्कि सुरक्षित भी होता है। इस लेख में हम Rust में मल्टीथ्रेडिंग की मूल बातें समझेंगे: थ्रेड बनाना, चैनल और म्यूटेक्स के माध्यम से डेटा का आदान-प्रदान, और 'Fearless Concurrency' मॉडल से परिचित होंगे।
1. मल्टीथ्रेडिंग की मूल बातें: std::thread
Rust मॉड्यूल std::thread के माध्यम से थ्रेड के लिए अंतर्निहित समर्थन प्रदान करता है। thread::spawn फ़ंक्शन का उपयोग करके एक नया थ्रेड बनाया जा सकता है, जो एक क्लोज़र लेता है।
use std::thread;use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("थ्रेड: {}", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("मुख्य थ्रेड: {}", i); thread::sleep(Duration::from_millis(1)); }
handle.join().unwrap(); // थ्रेड के समाप्त होने की प्रतीक्षा करें}join() विधि मुख्य थ्रेड को तब तक ब्लॉक करती है जब तक चाइल्ड थ्रेड समाप्त नहीं हो जाता। इसके बिना, प्रोग्राम थ्रेड के निष्पादित होने से पहले समाप्त हो सकता है।
2. थ्रेड के बीच डेटा स्थानांतरण: चैनल (mpsc)
Rust में थ्रेड के बीच संदेशों के आदान-प्रदान के लिए मल्टी-प्रोड्यूसर, सिंगल-कंज़्यूमर चैनल (mpsc — multiple producer, single consumer) मॉडल का उपयोग किया जाता है। यह मॉड्यूल std::sync::mpsc में कार्यान्वित है।
use std::sync::mpsc;use std::thread;
fn main() { let (tx, rx) = mpsc::channel();
thread::spawn(move || { let val = String::from("थ्रेड से नमस्ते!"); tx.send(val).unwrap(); // val अब स्थानांतरित हो चुका है, इसका उपयोग नहीं किया जा सकता });
let received = rx.recv().unwrap(); println!("प्राप्त: {}", received);}क्लोज़र में move कीवर्ड पर ध्यान दें — यह वेरिएबल tx का स्वामित्व नए थ्रेड को स्थानांतरित करता है। send() विधि एक Result लौटाती है, जो Err हो सकता है यदि रिसीवर बंद कर दिया गया हो।
कई प्रोड्यूसर बनाने के लिए tx को क्लोन किया जा सकता है:
let (tx, rx) = mpsc::channel();let tx1 = tx.clone();
thread::spawn(move || { tx.send("संदेश 1").unwrap();});
thread::spawn(move || { tx1.send("संदेश 2").unwrap();});
for received in rx { println!("प्राप्त: {}", received);}3. साझा मेमोरी: Mutex और Arc
चैनल डेटा स्थानांतरित करने के लिए सुविधाजनक हैं, लेकिन कभी-कभी थ्रेड के बीच स्थिति साझा करने की आवश्यकता होती है। इसके लिए Mutex (पारस्परिक अपवर्जन) का उपयोग Arc (परमाणु संदर्भ काउंटर) के साथ किया जाता है।
use std::sync::{Arc, Mutex};use std::thread;
fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![];
for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("परिणाम: {}", *counter.lock().unwrap());}Arc क्यों, Rc क्यों नहीं? सामान्य Rc थ्रेड-सुरक्षित नहीं है क्योंकि इसका संदर्भ काउंटर परमाणु नहीं है। Arc परमाणु संचालन का उपयोग करता है, जो इसे मल्टीथ्रेडिंग के लिए सुरक्षित बनाता है।
महत्वपूर्ण: Mutex lock() को कॉल करने पर थ्रेड को ब्लॉक करता है। दायरे से बाहर निकलने पर लॉक स्वचालित रूप से हटा दिया जाता है (Drop ट्रेट के कारण)।