C++ में समवर्तीता और मल्टीथ्रेडिंग: मूल बातों से आधुनिक मानकों तक
आधुनिक अनुप्रयोगों में अक्सर एक साथ कई कार्यों को निष्पादित करने की आवश्यकता होती है। चाहे वह सर्वर पर उपयोगकर्ता अनुरोधों को संसाधित करना हो, ग्राफिक्स रेंडर करना हो या बड़े डेटा के साथ काम करना हो — C++ में समवर्तीता और मल्टीथ्रेडिंग डेवलपर के लिए अनिवार्य कौशल बन गए हैं। इस लेख में, हम C++ मानक पुस्तकालय में उपलब्ध प्रमुख तंत्रों पर चर्चा करेंगे, C++11 से शुरू होकर C++20 तक। आप सीखेंगे कि थ्रेड कैसे बनाएं, उनके कार्य को सिंक्रोनाइज़ करें और सामान्य गलतियों से कैसे बचें।
यदि आप पहले से ही C++ की मूल बातों से परिचित हैं, लेकिन समानांतरता में गहराई से जाना चाहते हैं — यह सामग्री आपके लिए है। हम ऑपरेटिंग सिस्टम के सिद्धांत में गहराई से नहीं जाएंगे, बल्कि व्यावहारिक उपकरणों पर ध्यान केंद्रित करेंगे: std::thread, std::mutex, std::async और परमाणु संक्रियाएँ।
1. मल्टीथ्रेडिंग की मूल बातें: std::thread और थ्रेड प्रबंधन
C++11 में std::thread वर्ग के माध्यम से थ्रेड के लिए मानक समर्थन आया। कंस्ट्रक्टर में एक कॉल करने योग्य ऑब्जेक्ट — फ़ंक्शन, लैम्ब्डा या फ़ंक्टर पास करके एक नया थ्रेड बनाया जा सकता है।
#include <iostream>#include <thread>
void hello() { std::cout << "थ्रेड से नमस्ते " << std::this_thread::get_id() << std::endl;}
int main() { std::thread t(hello); // थ्रेड बनाना t.join(); // थ्रेड के समाप्त होने की प्रतीक्षा करना return 0;}प्रत्येक बनाए गए थ्रेड के लिए join() या detach() को कॉल करना हमेशा महत्वपूर्ण है, अन्यथा std::thread ऑब्जेक्ट के नष्ट होने पर प्रोग्राम क्रैश हो जाएगा। join() वर्तमान थ्रेड को चाइल्ड थ्रेड के समाप्त होने तक ब्लॉक करता है, जबकि detach() थ्रेड को स्वतंत्र रूप से काम करने देता है।
थ्रेड में आर्गुमेंट पास करने के लिए कंस्ट्रक्टर के अतिरिक्त पैरामीटर का उपयोग करें:
#include <thread>#include <string>
void print_sum(int a, int b, const std::string& msg) { std::cout << msg << ": " << (a + b) << std::endl;}
int main() { std::thread t(print_sum, 3, 4, "योग"); t.join(); return 0;}ध्यान दें: आर्गुमेंट थ्रेड के अंदर कॉपी हो जाते हैं। यदि संदर्भ पास करने की आवश्यकता है, तो std::ref() का उपयोग करें।
2. थ्रेड सिंक्रोनाइज़ेशन: म्यूटेक्स और लॉक
विभिन्न थ्रेड से साझा डेटा के साथ काम करते समय डेटा रेस (data race) उत्पन्न होता है। इससे बचने के लिए सिंक्रोनाइज़ेशन का उपयोग किया जाता है। मुख्य उपकरण std::mutex है।
#include <iostream>#include <thread>#include <mutex>
std::mutex mtx;int shared_counter = 0;
void increment() { for (int i = 0; i < 100000; ++i) { mtx.lock(); ++shared_counter; mtx.unlock(); }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "परिणाम: " << shared_counter << std::endl; return 0;}हालाँकि, मैन्युअल रूप से lock() और unlock() कॉल करना त्रुटियों से भरा है: यदि उनके बीच कोई अपवाद होता है, तो म्यूटेक्स लॉक रहेगा। RAII-रैपर std::lock_guard या std::unique_lock का उपयोग करना बेहतर है:
void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++shared_counter; } // दायरे से बाहर निकलने पर स्वचालित रूप से अनलॉक हो जाता है}अधिक लचीले नियंत्रण के लिए (जैसे, सशर्त लॉकिंग) std::unique_lock का उपयोग करें। और यदि डेडलॉक के बिना कई म्यूटेक्स को सिंक्रोनाइज़ करने की आवश्यकता है, तो std::lock() लागू करें।
3. एसिंक्रोनस कार्य: std::async और std::future
हमेशा थ्रेड को मैन्युअल रूप से प्रबंधित करना सुविधाजनक नहीं होता है।