“प्रति क्लाइंट एक CreateThread।” “टाइमर के लिए एक।” “इवेंट की प्रतीक्षा के लिए एक।” — नेटिव Windows कोड में थ्रेड इस तरह फैलते जाते हैं। हर थ्रेड स्टैक और कर्नेल ऑब्जेक्ट खर्च करता है, और बनाना-नष्ट करना भी लागत है। काम महीन है, थ्रेड भारी — उस बेमेल को सोखने के लिए OS जो देता है वह थ्रेड पूल है।
.NET के ThreadPool और Task.Run की सुविधा जानी-पहचानी है, पर वास्तव में Win32 नेटिव में भी अच्छी तरह डिज़ाइन की गई, OS-मानक थ्रेड-पूल API है। Windows Vista में व्यापक पुनर्डिज़ाइन के बाद, यह API नेटिव समवर्तिता की बुनियाद है: work, टाइमर, प्रतीक्षा और असिंक्रोनस I/O एकीकृत कॉलबैक तंत्र से सँभाल सकती है। C/C++ में Windows ऐप, सेवाएँ और DLL लिखने वाले डेवलपरों के लिए, यह लेख इस API की संरचना व उपयोग, और जिनमें फँसना आसान है उन जालों को, प्राथमिक स्रोतों पर आधारित समझाता है।
1. निष्कर्ष पहले
- बड़ी संख्या में अल्पकालिक काम जारी करने, और केवल-प्रतीक्षा थ्रेड बदलने के लिए, थ्रेड पूल अपने
CreateThreadसे बेहतर है। थ्रेड प्रबंधन OS पर छोड़ते हैं और थ्रेड संख्या व कॉन्टेक्स्ट स्विच घटा सकते हैं।1 - जो उपयोग करना चाहिए वह नई API है (
CreateThreadpoolWorkपरिवार)। Vista पुनर्डिज़ाइन ने इसे पुरानी API (QueueUserWorkItemपरिवार) से सरल, अधिक विश्वसनीय और उच्च-प्रदर्शन बनाया, और एक प्रोसेस में कई स्वतंत्र पूल भी बना सकते हैं।12 - चार प्रकार के ऑब्जेक्ट हैं। work, जिसमें काम जमा करते हैं; timer, जो समय या अवधि पर चलता है; wait, जो कर्नेल ऑब्जेक्ट सिग्नल होने पर चलता है; और io, जो असिंक्रोनस I/O पूर्ण होने पर चलता है। सभी एक ही कॉलबैक तंत्र पर सवार हैं।3
- शटडाउन है “प्रतीक्षा, फिर बंद”। चलते कॉलबैक पीछे न छोड़ने का अनुशासन —
WaitForThreadpoolWorkCallbacksपरिवार से पूर्णता की प्रतीक्षा, या क्लीनअप ग्रुप से थोक सँभालना — आवश्यक है।4 - कॉलबैक के भीतर: लंबे समय ब्लॉक न करें (यदि करेंगे,
CallbackMayRunLong); उसी पूल पर सिंक्रोनस पूर्णता प्रतीक्षा न करें; थ्रेड की अवस्था दूषित न करें। ये तीन लौह नियम हैं।56 - DLL से उपयोग: अनलोड दौड़ से बचें। स्पष्ट शटडाउन फ़ंक्शन में पूर्णता की प्रतीक्षा करें, और
FreeLibraryWhenCallbackReturnsजैसी समर्पित API जानें।3
2. पूल क्यों, और पूल कब
थ्रेड पूल का विचार सरल है। प्रति काम थ्रेड बनाने के बजाय, OS द्वारा प्रबंधित वर्कर थ्रेड के समूह पर काम (कॉलबैक) फेंकते हैं। वर्कर काम एक के बाद एक निष्पादित करते हैं, और OS संख्या को लोड के अनुसार समायोजित करता है।
आधिकारिक दस्तावेज़ उन ऐप के ठोस प्रकार गिनता है जहाँ पूल लाभ देता है।1
- ऐप जो बड़ी संख्या में छोटे वर्क आइटम समानांतर जारी करते हैं (खोज, नेटवर्क I/O, आदि)
- ऐप जो अल्पकालिक थ्रेड बार-बार बनाते और तोड़ते हैं
- ऐप जो पृष्ठभूमि में स्वतंत्र काम समानांतर संसाधित करते हैं
- ऐप जो कर्नेल ऑब्जेक्ट या इवेंट की प्रतीक्षा के लिए समर्पित थ्रेड रखते हैं
अंतिम बिंदु आसानी से छूट जाता है। यदि पाँच थ्रेड केवल इसलिए सोते हैं कि “इवेंट सिग्नल हो तो चलें”, उन्हें पूल पर पाँच wait ऑब्जेक्ट से बदला जा सकता है, और प्रतीक्षा पूल के वेटर थ्रेड पर एकत्र हो जाती है।
इसके विपरीत, ऐसा काम भी है जो पूल के अनुकूल नहीं। जिसे थ्रेड प्राथमिकता बदलनी हो, जिसे COM STA चाहिए, जो प्रोसेस के पूरे जीवन चलता रहे — जिस काम को थ्रेड पर “व्यक्तित्व” चाहिए वह समर्पित थ्रेड पर रहता है। वर्कर थ्रेड साझा संसाधन है; उधार लिया जाता है।
flowchart TB
accTitle: समर्पित थ्रेड और पूल का चुनाव
accDescr: पहले जाँचें कि प्राथमिकता या STA जैसी थ्रेड व्यक्तित्व चाहिए या नहीं, और क्या लंबे समय चलता है; जो न हो वह अल्पकालिक, अधिक-मात्रा या प्रतीक्षा-शैली काम थ्रेड पूल पर जाता है
q1{"प्राथमिकता या STA जैसी व्यक्तित्व?"} -->|"हाँ"| ded["समर्पित थ्रेड पर रखें"]
q1 -->|"नहीं"| q2{"लंबे समय चलता है?"}
q2 -->|"हाँ"| ded
q2 -->|"नहीं"| pool["थ्रेड पूल पर रखें"]
pool -.-> ex["अल्पकालिक काम, प्रतीक्षा, टाइमर, I/O"]
चित्र 1: पूल पर केवल वही काम रख सकते हैं जिसे “व्यक्तित्व नहीं चाहिए और जो जल्दी खत्म हो”। बाकी पहले की तरह समर्पित थ्रेड पर रहता है।
इतिहास का एक बिंदु भी पक्का करें। थ्रेड-पूल API की दो पीढ़ियाँ हैं। Windows 2000 से चली पुरानी API (QueueUserWorkItem, RegisterWaitForSingleObject आदि), और Vista में व्यापक पुनर्डिज़ाइन की नई API (CreateThreadpoolWork परिवार)। नई API वर्कर थ्रेड के प्रकार एक करती है, समर्पित स्थायी थ्रेड, एक प्रोसेस में कई पूल, क्लीनअप ग्रुप आदि देती है, और आधिकारिक दस्तावेज़ साफ़ कहता है कि यह “सरल, अधिक विश्वसनीय, बेहतर प्रदर्शन, अधिक लचीली” है।1 पुरानी API में “कतार में जाने के बाद काम रद्द नहीं कर सकते” जैसी संरचनात्मक बाधाएँ भी हैं।2 यहाँ से यह लेख केवल नई API सँभालता है।
flowchart TB
accTitle: पुरानी थ्रेड-पूल API और नई API का मेल
accDescr: पुरानी API का QueueUserWorkItem नई API के work ऑब्जेक्ट से, टाइमर कतारें timer से, पंजीकृत प्रतीक्षा wait से, और BindIoCompletionCallback io से मेल खाते हैं
o1["QueueUserWorkItem"] --> n1["work"]
o2["Timer queues"] --> n2["timer"]
o5["Registered waits"] --> n3["wait"]
o4["BindIoCompletionCallback"] --> n4["io"]
चित्र 2: पुरानी API से माइग्रेशन लक्ष्य एक-से-एक है। मौजूदा कोड की सूची इस मेल से शुरू हो सकती है।
3. चार ऑब्जेक्ट — work, timer, wait और io
नई API के केंद्र में चार प्रकार के ऑब्जेक्ट हैं जिनकी कॉलबैक-चलने की शर्तें अलग हैं।3
| ऑब्जेक्ट | निर्माण फ़ंक्शन | कॉलबैक कब चलता है |
|---|---|---|
| work | CreateThreadpoolWork | SubmitThreadpoolWork से जमा करने पर |
| timer | CreateThreadpoolTimer | निर्दिष्ट समय या अवधि आने पर |
| wait | CreateThreadpoolWait | कर्नेल ऑब्जेक्ट सिग्नल होने पर |
| io | CreateThreadpoolIo | जुड़े हैंडल पर असिंक्रोनस I/O पूर्ण होने पर |
flowchart TB
accTitle: थ्रेड पूल के चार ऑब्जेक्ट और कॉलबैक तंत्र
accDescr: work स्पष्ट जमा पर चलता है, timer समय पर, wait कर्नेल-ऑब्जेक्ट सिग्नल पर, और io असिंक्रोनस I/O पूर्णता पर; सभी एक ही वर्कर थ्रेड समूह पर कॉलबैक के रूप में चलते हैं
kind{"कौन सा ऑब्जेक्ट?"}
kind --> w["work(जमा पर)"]
kind --> more{"Timer, wait, या io?"}
more --> t["timer(समय / अवधि)"]
more --> rest{"Wait या io?"}
rest --> wt["wait(सिग्नल पर)"]
rest --> io["io(I/O पूर्णता)"]
w --> pool["वर्कर कॉलबैक चलाते हैं"]
t --> pool
wt --> pool
io --> pool
चित्र 3: चलने की शर्तें अलग हैं, पर चारों इस तंत्र में एक हैं कि “उसी पूल के वर्कर कॉलबैक चलाते हैं”।
यह एकीकरण व्यावहारिक शक्ति है। आवधिक प्रोसेसिंग, इवेंट प्रतिक्रिया और I/O-पूर्णता प्रोसेसिंग प्रत्येक को समर्पित थ्रेड पर लिखने के बजाय, उन्हें एक कॉलबैक शैली पर संरेखित कर सकते हैं। टाइमर पूरे पूल की एक टाइमर कतार में एकत्र होते हैं, और प्रतीक्षा थोड़े वेटर थ्रेड पर एकत्र होती है — प्रोसेस से “केवल सोने वाले” थ्रेड गायब हो जाते हैं।1
flowchart TB
accTitle: केवल-प्रतीक्षा थ्रेड को wait ऑब्जेक्ट से बदलना
accDescr: पहले प्रति इवेंट सोने वाले केवल-प्रतीक्षा थ्रेड wait ऑब्जेक्ट बन जाते हैं और पूल के वेटर थ्रेड पर एकत्र होते हैं, इसलिए कॉलबैक केवल सिग्नल होने पर चलता है
old2["5 समर्पित वेटर थ्रेड अलग सोते"] -.-> waste["5 स्टैक और 5 थ्रेड खर्च"]
new2["5 wait ऑब्जेक्ट"] --> agg["पूल के वेटर थ्रेड पर एकत्र"]
agg --> cb2["कॉलबैक केवल सिग्नल पर"]
चित्र 4: “केवल सोकर प्रतीक्षा” वाले थ्रेड wait ऑब्जेक्ट बनाकर हटाए जा सकते हैं। पूल माइग्रेशन की यह स्पष्ट पहली चाल है।
4. बुनियादी पैटर्न — work ऑब्जेक्ट से एक पूरा चक्कर
सबसे अधिक उपयोग होने वाले work ऑब्जेक्ट से शिष्टाचार एक बार चलते हैं।4
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE instance,
PVOID context, PTP_WORK work)
{
// The context is fixed at creation time. Per-item data is passed through a synchronised queue
WORK_QUEUE* queue = (WORK_QUEUE*)context;
ITEM* item = Dequeue(queue); // Take one item under exclusive control
ProcessItem(item);
}
// 1) Create (bind the callback to the shared context = the queue)
PTP_WORK work = CreateThreadpoolWork(WorkCallback, &queue, NULL);
if (!work) { /* Failure handling with GetLastError */ }
// 2) Submit once for each item you enqueue (keep the item count and the submit count in step)
Enqueue(&queue, item);
SubmitThreadpoolWork(work);
// 3) Stop the submitter, then wait for completion (TRUE also attempts to cancel work that has not yet started)
WaitForThreadpoolWorkCallbacks(work, FALSE);
// 4) Close
CloseThreadpoolWork(work);
दो बिंदु पकड़ें। पहला, एक ही work ऑब्जेक्ट पर SubmitThreadpoolWork एक से अधिक बार कर सकते हैं। हर जमा कॉलबैक चलाता है (समानांतर)।7 पर कॉलबैक को दिया गया context निर्माण समय पर तय होता है, इसलिए एक work ऑब्जेक्ट से “एक ही तरह के काम की N वस्तुएँ” लिखते समय, ऊपर के कोड की तरह context में सिंक्रोनाइज़्ड कतार रखें और प्रति जमा एक वस्तु लें (प्रति वस्तु work ऑब्जेक्ट बनाने वाला डिज़ाइन भी ठीक है)। दूसरा, बंद करने से पहले हमेशा पूर्णता की प्रतीक्षा करें। चलते या कतारबद्ध कॉलबैक रहते ऑब्जेक्ट बंद करना, या कॉलबैक जिस मेमोरी का संदर्भ ले उसे मुक्त करना, ज्यों का त्यों use-after-free है। इस प्रतीक्षा को सुरक्षित बंद बनाने के लिए पहले जमाकर्ता रोकना पूर्वशर्त है — ऐसी संरचना में जहाँ दूसरा थ्रेड प्रतीक्षा के समानांतर अभी Submit कर सकता हो, प्रतीक्षा के बाद का जमा Close से दौड़ता है। WaitForThreadpoolWorkCallbacks का दूसरा तर्क TRUE देने से अभी शुरू न हुए जमा रद्द करने का प्रयास भी होता है।
flowchart TB
accTitle: work ऑब्जेक्ट का जीवनचक्र
accDescr: CreateThreadpoolWork से बनाएँ; SubmitThreadpoolWork से जमा करें और कॉलबैक समानांतर चलें। शटडाउन पर पहले नए जमा रोकें, WaitForThreadpoolWorkCallbacks से हर कॉलबैक की पूर्णता की प्रतीक्षा करें, फिर CloseThreadpoolWork से बंद करें
c["CreateThreadpoolWork से बनाएँ"] --> s["SubmitThreadpoolWork से जमा(दोहराया जा सकता)"]
s --> run["कॉलबैक समानांतर चलते हैं"]
run --> stop3["नए जमा रोकें"]
stop3 --> w["WaitForThreadpoolWorkCallbacks से प्रतीक्षा"]
w --> cl["CloseThreadpoolWork से बंद करें"]
चित्र 5: शटडाउन क्रम है “जमा रोकें → पूर्णता की प्रतीक्षा → बंद”। कोई छोड़ें तो use-after-free या दौड़ मिलती है।
डिफ़ॉल्ट पर कॉलबैक प्रोसेस-डिफ़ॉल्ट पूल पर चलते हैं। कई उपयोगों के लिए वह पर्याप्त है। अगला अध्याय तब है जब पूल बाँटना चाहें।
5. कस्टम पूल और क्लीनअप ग्रुप
पूल बाँटें। CreateThreadpool से स्वतंत्र पूल बना सकते हैं और SetThreadpoolThreadMaximum / SetThreadpoolThreadMinimum से थ्रेड संख्या की ऊपरी व निचली सीमा सेट कर सकते हैं।8 विशिष्ट उपयोग अलगाव है। ताकि “धीमा हो सकने वाला बैच-जैसा काम” “तुरंत प्रतिक्रिया चाहिए वाले काम” के वर्कर न खा जाए, पूल बाँटें और प्रत्येक को अपना थ्रेड बजट दें।
कॉलबैक पर्यावरण से बाँधें। काम किस पूल पर चले यह TP_CALLBACK_ENVIRON (कॉलबैक पर्यावरण) आरंभ कर, SetThreadpoolCallbackPool से पूल की ओर इंगित कर, और उसे CreateThreadpoolWork आदि के तीसरे तर्क के रूप में देकर निर्दिष्ट होता है।9
क्लीनअप ग्रुप से मोड़ें। कई ऑब्जेक्ट बनाने वाले मॉड्यूल में शटडाउन प्रोसेसिंग “सभी की प्रतीक्षा, सभी बंद” का पाठ बन जाती है। यदि CreateThreadpoolCleanupGroup से ग्रुप बनाएँ और कॉलबैक पर्यावरण से हर ऑब्जेक्ट उससे जोड़ें, तो एक CloseThreadpoolCleanupGroupMembers हर सदस्य ऑब्जेक्ट की पूर्णता-प्रतीक्षा और रिलीज़ एक साथ करता है।34
flowchart TB
accTitle: कॉलबैक पर्यावरण से कॉन्फ़िगरेशन बाँधना
accDescr: कॉलबैक पर्यावरण कस्टम पूल और क्लीनअप ग्रुप की ओर इंगित करता है; उस पर्यावरण से बने work और timer उसी पूल पर चलते हैं, और क्लीनअप ग्रुप पर थोक ऑपरेशन पूर्णता-प्रतीक्षा व रिलीज़ एकत्र करता है
env["कॉलबैक पर्यावरण(TP_CALLBACK_ENVIRON)"] --> cp["कस्टम पूल(थ्रेड संख्या नियंत्रण)"]
env --> cg["क्लीनअप ग्रुप"]
env --> obj["work / timer / wait / io बनाते समय"]
cg -.-> close["एक झटके में प्रतीक्षा और रिलीज़"]
चित्र 6: कॉलबैक पर्यावरण वह तंत्र है जो ऑब्जेक्ट-निर्माण समय पर “किस पूल पर चले, और कौन साफ़ करे” इंजेक्ट करता है।
6. जाल — कॉलबैक के भीतर अनुशासन
लगभग हर थ्रेड-पूल बग “उधार थ्रेड पर मनमानी करने” से आता है।
लंबे समय ब्लॉक करना। पूल थ्रेड संख्या इस धारणा पर समायोजित करता है कि कॉलबैक तुरंत लौटें। डिफ़ॉल्ट पर लंबा काम या लंबी प्रतीक्षा अन्य कॉलबैक के निष्पादन में देरी करती है। लंबे चल सकने वाले कॉलबैक को CallbackMayRunLong से “यह लंबा चलेगा” घोषित करना चाहिए (पूल इसे थ्रेड जोड़ने का संकेत मानता है), या पहले से समर्पित थ्रेड पर भेजना चाहिए। ध्यान दें कि CallbackMayRunLong FALSE लौटाता है जब अन्य कॉलबैक के लिए वर्कर तैयार नहीं कर सकता। रिटर्न वैल्यू जाँचे बिना ब्लॉक करते रहें तो पूल फिर भी जाम होता है, इसलिए FALSE हो तो उस पक्ष पर जाएँ जो ब्लॉक न करे — काम बाँटें, समर्पित थ्रेड पर भेजें, आदि।5
उसी पूल पर सिंक्रोनस पूर्णता प्रतीक्षा। ऐसा रूप जहाँ कॉलबैक A के भीतर उसी पूल पर जमा काम B की पूर्णता की WaitForThreadpoolWorkCallbacks आदि से प्रतीक्षा हो, उस क्षण पूल-स्टारवेशन डेडलॉक बन जाता है जब हर वर्कर “दूसरे वर्कर की प्रतीक्षा” में हो। कामों के बीच निर्भरता को प्रतीक्षा नहीं, बल्कि “B के पूर्णता कॉलबैक से अगला जमा करें” जैसी निरंतरता के रूप में लिखें।
flowchart TB
accTitle: पूल-स्टारवेशन डेडलॉक की संरचना
accDescr: यदि हर वर्कर थ्रेड उसी पूल पर जमा अन्य काम की पूर्णता की सिंक्रोनस प्रतीक्षा करे, तो उस काम को चलाने के लिए खाली वर्कर नहीं बचता, और सब सदा प्रतीक्षा करते रहते हैं
w1["वर्कर 1: काम X की प्रतीक्षा"] --> q["काम X और Y चलने की प्रतीक्षा"]
w2["वर्कर 2: काम Y की प्रतीक्षा"] --> q
q -.-> none["उन्हें चलाने को खाली वर्कर नहीं"]
none -.-> dead["सब सदा प्रतीक्षा(स्टारवेशन डेडलॉक)"]
चित्र 7: यदि वर्कर के भीतर से वर्कर की सिंक्रोनस प्रतीक्षा करें, तो प्रतीक्षित काम चलाने वाला कोई नहीं बचता।
थ्रेड की अवस्था दूषित करना। वर्कर थ्रेड अगले कॉलबैक के लिए पुनः उपयोग होता है। थ्रेड प्राथमिकता बदलना, COM आरंभीकरण अवस्था, TLS में छोड़ा मान, छोड़ना भूल गया लॉक — इनमें से कोई भी अगले (असंबंधित) कॉलबैक का दूषण बनता है। “पूल पर फेंकी गई फ़ंक्शन थ्रेड की व्यक्तित्व पर निर्भर न हो” पुरानी-API काल से आधिकारिक सावधानी है।6 क्लीनअप के समर्पित तंत्र हैं; उदाहरण के लिए, LeaveCriticalSectionWhenCallbackReturns पूल से कह सकता है कि “यह कॉलबैक लौटे तो यह लॉक छोड़ दें”।3
DLL अनलोड से दौड़। कॉलबैक चलते कोड वाला DLL अनलोड हो तो एक्सेस वायलेशन होता है। बुनियादी रूप है DLL की शटडाउन फ़ंक्शन में पूर्णता की पूरी प्रतीक्षा; FreeLibraryWhenCallbackReturns उस स्थिति के लिए है “यह कॉलबैक अंतिम काम है, और पूरा होने पर मैं स्वयं सहित DLL मुक्त करना चाहता हूँ”। पर यह API केवल “चलते कॉलबैक के लौटने पर एक संदर्भ छोड़ती है”; कॉलबैक शुरू होने से पहले अनलोड नहीं रोकती। इसे जोड़ के रूप में उपयोग करें: जमा से पहले GetModuleHandleEx से अपना मॉड्यूल संदर्भ लें, और कॉलबैक उस संदर्भ को इस API से छोड़े।3 और यह पूर्णता-प्रतीक्षा DllMain के भीतर न करें — “DllMain और Loader Lock” में कहा गया है, DllMain के भीतर दूसरे थ्रेड की प्रतीक्षा डेडलॉक पैटर्न है।
flowchart TB
accTitle: DLL अनलोड और कॉलबैक की दौड़ की तैयारी
accDescr: कॉलबैक चलते DLL अनलोड एक्सेस वायलेशन बनता है, इसलिए बुनियादी रूप स्पष्ट शटडाउन फ़ंक्शन में पूर्णता की प्रतीक्षा कर फिर बंद करना है; जब अंतिम कॉलबैक स्वयं DLL मुक्त करे, FreeLibraryWhenCallbackReturns उपयोग करें
risk["कॉलबैक के दौरान अनलोड"] -.-> av["एक्सेस वायलेशन"]
g1["प्रतीक्षा, फिर बंद"] --> safe["सुरक्षित अनलोड"]
g1 -.-> g1N["शटडाउन फ़ंक्शन में"]
g2["FreeLibraryWhenCallbackReturns"] --> safe
g2 -.-> g2n["अंतिम कॉलबैक DLL मुक्त करे"]
g1 -.-> ng["DllMain के भीतर नहीं"]
av ~~~ g1
चित्र 8: बुनियादी रूप स्पष्ट शटडाउन फ़ंक्शन में “प्रतीक्षा, फिर बंद” करना है। DllMain के भीतर प्रतीक्षा अलग डेडलॉक बुलाती है।
जमा काम के भीतर अपवाद और क्रैश। वर्कर थ्रेड पर अनहैंडल्ड अपवाद प्रोसेस साथ ले जाता है। समर्पित थ्रेड की थ्रेड फ़ंक्शन की तरह, कॉलबैक के प्रवेश पर अपवाद व्यापक रूप से पकड़ने और लॉग करने की नीति लागू करें।
7. मानक लाइब्रेरी और .NET से संबंध — किस परत पर लिखें
अंत में, अन्य उपकरणों से यह कहाँ बैठता है यह छाँटते हैं।
- यदि C++
std::async/std::threadपर्याप्त हों, वे पहला उम्मीदवार हैं। वे पोर्टेबल हैं, कोड छोटा है, औरfutureके अर्थ भी मानक तय करते हैं।10 - Win32 थ्रेड पूल सीधे उपयोग करने के कारण तब हैं जब आप (1) timer, wait और io सहित एकीकृत कॉलबैक तंत्र चाहते हैं, (2) पूल विभाजन या थ्रेड-संख्या नियंत्रण चाहते हैं, या (3) DLL या COM कॉम्पोनेंट के भीतर अपने थ्रेड नहीं रखना चाहते।
- .NET पक्ष पर,
ThreadPoolऔरTaskवही भूमिका निभाते हैं, और I/O पूर्णता IOCP से बँधी है। यह तहखाने की संरचना “IOCP और .NET Thread Pool” में समझाई गई है।
flowchart TB
accTitle: किस परत के उपकरणों से लिखें यह तय करना
accDescr: यदि मानक C++ async या thread पर्याप्त हों तो वे उपयोग करें; Win32 थ्रेड पूल सीधे तब जब टाइमर, प्रतीक्षा और I/O पूर्णता का एकीकरण, पूल विभाजन या थ्रेड-संख्या नियंत्रण चाहिए, या DLL या COM में अपने थ्रेड न चाहें
q1{"मानक C++ उपकरण पर्याप्त?"} -->|"हाँ"| std["std::async / std::thread"]
q1 -->|"नहीं"| q2{"क्या चाहिए?"}
q2 -->|"timer, wait और io का एकीकरण"| tp["Win32 थ्रेड पूल"]
q2 -->|"पूल विभाजन / संख्या नियंत्रण"| tp
q2 -->|"DLL में अपने थ्रेड से बचना"| tp
चित्र 9: संदेह हो तो मानक लाइब्रेरी से शुरू करें; इस API की बारी तब आती है जब वह जो व्यक्त न कर सके वह आवश्यकता आए।
अर्थात्, यह API समवर्तिता की बुनियाद उस बिंदु पर है जब आपने “नेटिव लिखना” तय कर लिया हो। यथार्थवादी सफ़ाई चरणबद्ध है: अपने CreateThread के फैलाव से माइग्रेशन लक्ष्य के रूप में, पहले work ऑब्जेक्ट लाएँ, फिर केवल-प्रतीक्षा थ्रेड को wait और टाइमर थ्रेड को timer से बदलें।
8. सारांश
- बड़ी संख्या में अल्पकालिक काम जारी करने, और केवल-प्रतीक्षा व केवल-टाइमर थ्रेड साफ़ करने के लिए, अपने थ्रेड के बजाय OS-मानक थ्रेड पूल। उपयोग Vista के बाद की नई API है।
- केंद्र में चार ऑब्जेक्ट work, timer, wait और io हैं। चलने की शर्तें अलग; वे एक ही वर्कर समूह और एक ही कॉलबैक शैली पर एक हैं।
- शिष्टाचार है “बनाएँ → जमा करें → पूर्णता की प्रतीक्षा → बंद”। कई जमा समानांतर चलते हैं। क्लीनअप ग्रुप शटडाउन प्रोसेसिंग थोक कर सकता है।
- कॉलबैक के तीन लौह नियम: लंबे समय ब्लॉक न करें (यदि करेंगे,
CallbackMayRunLong); उसी पूल पर सिंक्रोनस प्रतीक्षा न करें; थ्रेड की अवस्था दूषित न करें। - DLL से उपयोग: अनलोड दौड़ से बचें। स्पष्ट शटडाउन फ़ंक्शन में पूर्णता की प्रतीक्षा करें;
DllMainमें न करें। - जहाँ मानक C++ या .NET पर्याप्त हों, वे उपयोग करें। इस API की बारी तब है जब timer/wait/io का एकीकरण या पूल नियंत्रण चाहिए।
थ्रेड-पूल API, Win32 API में, नई और बेहतर-डिज़ाइन वाली ओर है। “थ्रेड बनाने” के विचार से “कॉलबैक फेंकने” के विचार तक स्थानांतरण पूरा होने पर, नेटिव कोड में समवर्तिता काफी साफ़ लिखी जाती है।
संबंधित लेख
- Windows I/O की गहराई (भाग 3) — I/O Completion Ports (IOCP) और .NET Thread Pool: async/await के नीचे की तह
- व्यावहारिक मल्टीथ्रेडिंग सर्वोत्तम अभ्यास: C++ संस्करण — RAII और jthread से संरचना द्वारा दुर्घटनाएँ हटाना
- व्यावहारिक मल्टीथ्रेडिंग सर्वोत्तम अभ्यास: C संस्करण — Win32 API तरीके से सुरक्षित लिखना
- स्प्यूरियस वेकअप — Condition Variable “बिना सूचना के” क्यों जागती है और Windows पर सही प्रतीक्षा
- DllMain और Loader Lock — DLL आरंभीकरण में “कुछ न करें” कहे जाने का वास्तविक कारण
- Windows पर Sleep(1) के बजाय Event Wait क्यों चुनें
संबंधित परामर्श क्षेत्र
KomuraSoft LLC उन नेटिव कोड से थ्रेड पूल पर माइग्रेशन डिज़ाइन सँभालता है जिनके थ्रेड फैल गए हैं, C++ ऐप और DLL में समवर्ती प्रोसेसिंग की डिज़ाइन समीक्षा, और पूल स्टारवेशन या कॉलबैक से आए हैंग व क्रैश की मूल-कारण जाँच। मौजूदा कोड की सूची से परामर्श स्वागत योग्य है।
संदर्भ लिंक
-
Microsoft Learn, Thread Pools. थ्रेड पूल वर्कर थ्रेड का संग्रह होने पर जो ऐप की ओर से असिंक्रोनस कॉलबैक दक्षता से निष्पादित करता है; जिन ऐप प्रकारों के अनुकूल है (बड़ी संख्या में छोटे वर्क आइटम समानांतर जारी करना, अल्पकालिक थ्रेड बार-बार बनाना-तोड़ना, स्वतंत्र काम समानांतर संसाधित करना, कर्नेल ऑब्जेक्ट पर विशेष प्रतीक्षा, आदि); और व्यापक Vista पुनर्डिज़ाइन (वर्कर-थ्रेड प्रकारों का एकीकरण, एक टाइमर कतार, समर्पित स्थायी थ्रेड, क्लीनअप ग्रुप, एक प्रोसेस में कई पूल, और नई API) पर। ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Thread Pooling. पुरानी थ्रेड-पूल API की संरचना पर (QueueUserWorkItem, टाइमर कतारें, पंजीकृत प्रतीक्षा, BindIoCompletionCallback); कतार में जाने के बाद काम रद्द करने का तरीका न होने पर; और Vista में आई नई थ्रेड-पूल API के सरल तथा विश्वसनीयता, प्रदर्शन व लचीलेपन में श्रेष्ठ बताए जाने पर। ↩ ↩2
-
Microsoft Learn, threadpoolapiset.h header. चार ऑब्जेक्ट-निर्माण फ़ंक्शन CreateThreadpoolWork, CreateThreadpoolTimer, CreateThreadpoolWait और CreateThreadpoolIo सहित फ़ंक्शन सूची पर; क्लीनअप ग्रुप (CreateThreadpoolCleanupGroup); और कॉलबैक पूर्णता से बँधे क्लीनअप (LeaveCriticalSectionWhenCallbackReturns, FreeLibraryWhenCallbackReturns, आदि) पर। ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Using the Thread Pool Functions. CreateThreadpoolWork से बनाने, SubmitThreadpoolWork से जमा करने, WaitForThreadpoolWorkCallbacks से पूर्णता की प्रतीक्षा, और CloseThreadpoolWork से बंद करने की बुनियादी प्रक्रिया पर; और कस्टम पूल को कॉलबैक पर्यावरण व क्लीनअप ग्रुप से जोड़ने वाले कॉन्फ़िगरेशन उदाहरण पर। ↩ ↩2 ↩3
-
Microsoft Learn, CallbackMayRunLong function (threadpoolapiset.h). पूल को सूचित करने पर कि वर्तमान कॉलबैक लंबे समय चल सकता है, ताकि पूल अन्य कॉलबैक के लिए थ्रेड सुरक्षित करने का निर्णय ले सके; और जहाँ संभव हो लंबे-चलने वाले कॉलबैक के लिए समर्पित थ्रेड पर विचार करने पर। ↩ ↩2
-
Microsoft Learn, Thread Pooling. थ्रेड पूल पर जमा वर्क आइटम, और वे जिन फ़ंक्शन को कॉल करते हैं, के थ्रेड-पूल सुरक्षित होने पर; निष्पादन थ्रेड को समर्पित स्थायी थ्रेड न मानने पर; और TLS तथा स्थायी थ्रेड चाहिए वाली असिंक्रोनस कॉल से बचने पर। ↩ ↩2
-
Microsoft Learn, SubmitThreadpoolWork function (threadpoolapiset.h). एक ही work ऑब्जेक्ट को पूर्ववर्ती कॉलबैक की पूर्णता की प्रतीक्षा किए बिना एक से अधिक बार जमा कर सकने पर, जिससे कॉलबैक समानांतर चलें; और पूल के दक्षता के लिए थ्रेड संख्या समायोजित (थ्रॉटल) कर सकने पर। ↩
-
Microsoft Learn, SetThreadpoolThreadMaximum function (threadpoolapiset.h). CreateThreadpool से बने पूल की वर्कर-थ्रेड संख्या पर ऊपरी सीमा सेट कर सकने पर (निचली सीमा SetThreadpoolThreadMinimum है)। ↩
-
Microsoft Learn, CreateThreadpoolWork function (threadpoolapiset.h). कॉलबैक फ़ंक्शन और context पॉइंटर से work ऑब्जेक्ट बनाने पर; और तीसरे तर्क TP_CALLBACK_ENVIRON के कॉलबैक का निष्पादन पर्यावरण (संबंधित पूल आदि) निर्दिष्ट कर सकने पर, NULL का अर्थ डिफ़ॉल्ट पर्यावरण में चलना। ↩
-
Microsoft Learn, <future>. std::async और future के माध्यम से प्रति कार्य असिंक्रोनस निष्पादन मानक लाइब्रेरी के रूप में दिए जाने पर, ताकि थ्रेड सीधे प्रबंधित किए बिना समवर्तिता लिख सकें। ↩
संबंधित लेख
निकटवर्ती विषयों में गहराई से जाने के लिए समान टैग वाले नवीनतम लेख।
Named Pipes व्यवहार में — डिज़ाइन से सुरक्षा तक, Windows की मानक IPC
Named pipes — Windows की मानक इंटर-प्रोसेस कम्युनिकेशन — की व्यावहारिक मार्गदर्शिका। यह लेख प्राथमिक स्रोतों से बाइट और मैसेज मोड का चुना...
Windows वर्चुअलाइज़ेशन की गहराई (भाग 3) — सेकंडों में बूट होने वाली वर्चुअल मशीनें: WSL2, Windows Sandbox और कंटेनर इतने हल्के क्यों हैं
WSL2 और Windows Sandbox सेकंडों में शुरू होकर इतने हल्के क्यों लगते हैं? यह लेख डायनामिक बेस इमेज और डायरेक्ट मैप से डायनामिक मेमोरी आवंट...
Windows वर्चुअलाइज़ेशन की गहराई (भाग 2) — वह मेमोरी जिसे कर्नेल भी नहीं देख सकता: VBS, HVCI और Credential Guard कैसे काम करते हैं
संगत हार्डवेयर पर क्लीन इंस्टॉल पर VBS डिफ़ॉल्ट से सक्षम होता है और हाइपरवाइज़र तथा SLAT से कर्नेल से मज़बूत अलगाव बनाता है। यह लेख VTL, ...
Windows वर्चुअलाइज़ेशन की गहराई (भाग 1) — आपका Windows वास्तव में कहाँ चल रहा है? हाइपरवाइज़र और पार्टीशन
जब आप Hyper-V सक्षम करते हैं, तो होस्ट Windows स्वयं रूट पार्टीशन के रूप में हाइपरवाइज़र के ऊपर चलता है। यह लेख VT-x, SLAT और VMBus की भू...
क्षेत्र के नाम वाली खोजों में दिखें — छोटे-मझोले उद्यमों के लिए लोकल SEO की व्यावहारिक मार्गदर्शिका (क्षेत्र पृष्ठ और Google Business Profile)
उन छोटे-मझोले उद्यमों के लिए जिनकी साइट "क्षेत्र का नाम + उद्योग" खोजने पर नहीं दिखती। यह लेख लोकल SEO सुधारने का क्रम बताता है: Google B...
संबंधित विषय
ये पृष्ठ विषय को सेवाओं और निर्णयों के व्यापक संदर्भ में रखते हैं।
Windows के तकनीकी विषय
Windows विकास, बग जाँच और मौजूदा संपत्तियों के उपयोग का प्रवेश-द्वार।
इस विषय से जुड़ी सेवाएँ
यह लेख निम्नलिखित सेवाओं से सीधे जुड़ा है।
Windows ऐप विकास
व्यावसायिक ऐप, डिवाइस एकीकरण और संचार उपकरण, आवश्यकताओं से विकास तक।
अक्सर पूछे जाने वाले प्रश्न
इस लेख के विषय पर परामर्श में अक्सर पूछे जाने वाले प्रश्न।
- CreateThread से अपने थ्रेड बनाने की तुलना में थ्रेड पूल बेहतर क्यों है?
- जब बड़ी संख्या में अल्पकालिक काम हों तो दक्षता, और थ्रेड-प्रबंधन कोड में कमी। थ्रेड बनाना और नष्ट करना अनदेखा न करने योग्य लागत है, इसलिए जो ऐप "हर काम के लिए CreateThread और पूरा होने पर नष्ट" दोहराता है, या केवल इवेंट की प्रतीक्षा में सोने वाले कई थ्रेड रखता है, वह पूल पर जाने से थ्रेड संख्या और कॉन्टेक्स्ट स्विच घटा सकता है। आधिकारिक दस्तावेज़ पूल उम्मीदवारों में उन ऐप को गिनता है जो बड़ी संख्या में छोटे वर्क आइटम समानांतर जारी करते हैं, जो कई अल्पकालिक थ्रेड बनाते हैं, और जिनके थ्रेड केवल कर्नेल ऑब्जेक्ट की प्रतीक्षा के लिए समर्पित हैं। इसके विपरीत, वह काम जिसे "थ्रेड पर अपनी व्यक्तित्व चाहिए" — प्राथमिकता बदलना, COM STA, लंबे समय चलने वाली समर्पित प्रोसेसिंग — पहले की तरह समर्पित थ्रेड पर ही रखना चाहिए।
- QueueUserWorkItem जैसी पुरानी थ्रेड-पूल फ़ंक्शन से यह कैसे अलग है?
- थ्रेड पूल Windows Vista में व्यापक रूप से पुनर्डिज़ाइन हुआ। वर्तमान threadpoolapiset-परिवार API (CreateThreadpoolWork आदि) नई API हैं; QueueUserWorkItem, RegisterWaitForSingleObject आदि पुरानी (लेगेसी) API हैं। नई API वर्कर थ्रेड के प्रकार एक करती है, एक प्रोसेस में कई स्वतंत्र पूल बनाने देती है, और क्लीनअप ग्रुप से थोक रिलीज़ तथा कॉलबैक पूर्णता से बँधे लॉक रिलीज़ या DLL अनलोड जैसे तंत्र देती है। आधिकारिक दस्तावेज़ यह भी कहता है कि नई API सरल है और विश्वसनीयता, प्रदर्शन व लचीलेपन में श्रेष्ठ है। पुरानी API में संरचनात्मक बाधाएँ भी हैं, जैसे "कतार में जाने के बाद काम रद्द करने का कोई तरीका नहीं", इसलिए नए कोड में नई API उपयोग करें।
- कॉलबैक के भीतर ऐसी बातें हैं जो नहीं करनी चाहिए?
- तीन बड़ी हैं। पहली, डिफ़ॉल्ट पर लंबे समय ब्लॉक करना या लंबा काम करना। पूल थ्रेड संख्या इस धारणा पर समायोजित करता है कि कॉलबैक तुरंत पूरे हों, इसलिए लंबे काम के लिए या तो CallbackMayRunLong से घोषित करें या समर्पित थ्रेड उपयोग करें। दूसरी, उसी पूल पर जमा किए अन्य काम की पूर्णता की सिंक्रोनस प्रतीक्षा। यदि हर वर्कर "दूसरे वर्कर की प्रतीक्षा" में फँस जाए, तो पूल-स्टारवेशन डेडलॉक होता है। तीसरी, थ्रेड की व्यक्तित्व पर निर्भरता। वर्कर थ्रेड कॉलबैक के बीच साझा होते हैं, इसलिए बदली थ्रेड प्राथमिकता या COM आरंभीकरण अवस्था लेकर लौटना, या TLS में अवस्था छोड़ना, अगले कॉलबैक को दूषित करता है। अंत में क्लीनअप (लॉक छोड़ना या DLL अनलोड) के लिए LeaveCriticalSectionWhenCallbackReturns और FreeLibraryWhenCallbackReturns जैसे समर्पित तंत्र हैं।
- DLL से थ्रेड पूल उपयोग करते समय क्या ध्यान रखें?
- सबसे बड़ा खतरा है "कॉलबैक अभी चल रहा हो और DLL अनलोड हो जाए"। अनलोड के बाद कॉलबैक चले तो एक्सेस वायलेशन होता है। DLL पक्ष को शटडाउन प्रोसेसिंग में अपने जारी कॉलबैक की पूर्णता की विश्वसनीय प्रतीक्षा करनी चाहिए — WaitForThreadpoolWorkCallbacks जैसी प्रतीक्षा फ़ंक्शन से, या क्लीनअप ग्रुप पर CloseThreadpoolCleanupGroupMembers से — और तभी ऑब्जेक्ट बंद करें। पर DllMain के भीतर यह प्रतीक्षा लोडर लॉक से अंतःक्रिया कर डेडलॉक कर सकती है, इसलिए नियम है इसे DllMain में नहीं, स्पष्ट शटडाउन फ़ंक्शन में करना। उस स्थिति के लिए जब कॉलबैक स्वयं DLL मुक्त करना चाहे क्योंकि "यह काम अंतिम है", समर्पित API FreeLibraryWhenCallbackReturns दी गई है।
- अब C++ std::async और .NET का ThreadPool होने पर भी इस API को सीधे उपयोग करने के अवसर हैं?
- हाँ। कसौटी है "क्या उस परत का उपकरण पर्याप्त है"। यदि C++ में चाहिए समवर्तिता की ग्रैन्युलैरिटी std::async या std::thread से ढक जाए, तो पोर्टेबिलिटी की दृष्टि से भी मानक लाइब्रेरी पहला उम्मीदवार है। दूसरी ओर, टाइमर, कर्नेल-ऑब्जेक्ट प्रतीक्षा और असिंक्रोनस I/O पूर्णता को एक कॉलबैक तंत्र में एक करना; काम के प्रकार के अनुसार पूल बाँटना और थ्रेड संख्या नियंत्रित करना; DLL या COM कॉम्पोनेंट के भीतर अपने थ्रेड न रखना — ये आवश्यकताएँ Win32 थ्रेड पूल पूरी करता है। .NET के ThreadPool और IOCP से संबंध संबंधित लेख में है, और जब तक नेटिव लिख रहे हैं, नीचे की परत पर बैठा यह तंत्र जानना व्यर्थ नहीं।