Rust में समवर्तीता: म्यूटेक्स से async/await तक
\n\nजब उच्च-प्रदर्शन प्रणालियों की बात आती है, तो Rust तुरंत ध्यान का केंद्र बन जाता है। लेकिन वास्तव में इसे अद्वितीय क्या बनाता है? यह है Rust में समवर्तीता — एक दृष्टिकोण जिसमें आपको C की गति और मेमोरी सुरक्षा मिलती है, बिना कोड की पठनीयता से समझौता किए। इस लेख में, हम तीन मुख्य उपकरणों पर चर्चा करेंगे: क्लासिकल थ्रेड्स, हल्के गोरूटीन्स (Rust के संदर्भ में — एसिंक्रोनस कार्य) और आधुनिक async/await। चलिए शुरू करते हैं!
\n\nRust समवर्तीता से क्यों नहीं डरता?
\nकई भाषाओं के विपरीत, Rust कंपाइलर स्तर पर डेटा रेस की समस्या को हल करता है। स्वामित्व (ownership) और उधार (borrowing) प्रणाली गारंटी देती है कि आप गलती से दो थ्रेड्स से एक साथ डेटा नहीं बदल सकते। यह जादू नहीं है — यह सख्त स्थैतिक जाँच है। यही कारण है कि Rust में मल्टीथ्रेडिंग को सबसे सुरक्षित माना जाता है।
\n\n1. थ्रेड्स (std::thread)
\nक्लासिकल OS थ्रेड्स — यह आधार है। Rust में इन्हें मॉड्यूल std::thread के माध्यम से लागू किया गया है। प्रत्येक थ्रेड एक अलग हल्का प्रक्रिया है जो CPU के विभिन्न कोर पर समानांतर रूप से निष्पादित हो सकता है।
उदाहरण: थ्रेड्स शुरू करना
\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n let handle = thread::spawn(|| {\n for i in 1..10 {\n println!("थ्रेड: {}", i);\n thread::sleep(Duration::from_millis(1));\n }\n });\n\n for i in 1..5 {\n println!("मुख्य थ्रेड: {}", i);\n thread::sleep(Duration::from_millis(1));\n }\n\n handle.join().unwrap();\n}\n\nयहाँ thread::spawn एक नया थ्रेड बनाता है, और join इसके पूरा होने की प्रतीक्षा करता है। सरल है, लेकिन एक बारीकी है: थ्रेड्स 'भारी' होते हैं। प्रत्येक थ्रेड कम से कम 2-8 MB स्टैक मेमोरी की खपत करता है। हजारों एक साथ कार्यों के लिए यह उपयुक्त नहीं है।
थ्रेड्स का उपयोग कब करें?
\n- \n
- वास्तविक समानांतरता की आवश्यकता (CPU-बाउंड कार्य)। \n
- थ्रेड्स की संख्या कोर की संख्या * 2 से अधिक न हो। \n
- आप ब्लॉकिंग ऑपरेशन (फ़ाइल पढ़ना, सिंक्रोनस कॉल) के साथ काम कर रहे हैं। \n
2. Rust में गोरूटीन्स: मिथक या वास्तविकता?
\nसख्ती से कहें तो, Rust में Go जैसे गोरूटीन्स नहीं हैं। लेकिन इसमें एसिंक्रोनस कार्य (tasks) हैं, जो लगभग उसी तरह व्यवहार करते हैं: वे हल्के होते हैं, सहकारी रूप से स्विच करते हैं (OS के बिना), और एक ही थ्रेड पर हजारों की संख्या में चलाए जा सकते हैं। इसके लिए रनटाइम Tokio या async-std का उपयोग किया जाता है।
\n\nउदाहरण: Tokio के माध्यम से एसिंक्रोनस कार्य (गोरूटीन)
\nuse tokio::task;\n\n#[tokio::main]\nasync fn main() {\n let handle = task::spawn(async {\n for i in 1..10 {\n println!("गोरूटीन: {}", i);\n tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;\n }\n });\n\n for i in 1..5 {\n println!("मुख्य: {}", i);\n tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;\n }\n\n handle.await.unwrap();\n}\n\nध्यान दें: task::spawn एक हल्का कार्य बनाता है जो Tokio के थ्रेड पूल के भीतर निष्पादित होता है। मेमोरी लागत — प्रति कार्य लगभग 2-3 KB। यह बिना किसी समस्या के 100,000 'गोरूटीन्स' चलाने की अनुमति देता है। Go के विपरीत, यहाँ कोई अंतर्निहित शेड्यूलर नहीं है — आप स्वयं रनटाइम चुनते हैं।
Rust के गोरूटीन्स Go से कैसे भिन्न हैं?
\n- \n
- Rust में — कार्य सहकारी हैं: वे स्वयं
.awaitके माध्यम से नियंत्रण छोड़ते हैं। \n - Go में — गोरूटीन्स प्रीएम्प्टिव हैं: शेड्यूलर I/O बिंदुओं पर उन्हें स्वयं बाधित करता है। \n
- Rust अधिक नियंत्रण देता है: आप सिंगल-थ्रेडेड या मल्टी-थ्रेडेड रनटाइम चुन सकते हैं। \n
3. Async/await: एसिंक्रोनस Rust का हृदय
\nकीवर्ड async और .await सिंक्रोनस कोड को बिना किसी कठिनाई के एसिंक्रोनस में बदल देते हैं।