यहां कम से कम तीन अवधारणाएं हैं, जिनमें से सभी बोलचाल भाषा में उलझन में हैं, जो कि आप उलझन में थे।
- धागा सुरक्षित
- महत्वपूर्ण अनुभाग
- फिर से प्रवेशी
सबसे आसान एक पहले लेने के लिए: दोनों malloc
और printf
thread-safe हैं। उन्हें 2001 से मानक सी में थ्रेड-सुरक्षित होने की गारंटी दी गई है, 2001 से पॉज़िक्स में, और इससे पहले से ही अभ्यास में। इसका मतलब है कि निम्नलिखित कार्यक्रम दुर्घटना या प्रदर्शन करने के लिए नहीं बुरा व्यवहार की गारंटी है है:
#include <pthread.h>
#include <stdio.h>
void *printme(void *msg) {
while (1)
printf("%s\r", (char*)msg);
}
int main() {
pthread_t thr;
pthread_create(&thr, NULL, printme, "hello");
pthread_create(&thr, NULL, printme, "goodbye");
pthread_join(thr, NULL);
}
एक समारोह जो थ्रेड-सुरक्षित नहींstrtok
है का एक उदाहरण। यदि आप strtok
को दो अलग-अलग धागे से एक साथ कॉल करते हैं, तो परिणाम अपरिभाषित व्यवहार है - क्योंकि strtok
आंतरिक रूप से अपने राज्य का ट्रैक रखने के लिए एक स्थिर बफर का उपयोग करता है। glibc इस समस्या को हल करने के लिए strtok_r
जोड़ता है, और सी 11 ने एक ही चीज़ को जोड़ा (लेकिन वैकल्पिक रूप से और एक अलग नाम के तहत, क्योंकि यहां खोज नहीं किया गया) strtok_s
के रूप में।
ठीक है, लेकिन printf
अपने आउटपुट का निर्माण करने के लिए वैश्विक संसाधनों का भी उपयोग नहीं करता है? वास्तव में, यह का मतलब क्या होगा दो धागे से stdout पर प्रिंट करने के लिए? जो हमें अगले विषय पर लाता है। जाहिर है printf
इसका उपयोग करने वाले किसी भी प्रोग्राम में critical section होने जा रहा है। एक बार में महत्वपूर्ण अनुभाग के अंदर निष्पादन का केवल एक थ्रेड होने की अनुमति है।
कम से कम POSIX अनुरूप प्रणालियों में, इस printf
flockfile(stdout)
के लिए एक कॉल के साथ शुरू और funlockfile(stdout)
के लिए एक कॉल है, जो एक वैश्विक stdout के साथ जुड़े म्युटेक्स लेने की तरह मूल रूप से है के साथ समाप्त होने से हासिल की है।
हालांकि, कार्यक्रम में प्रत्येक विशिष्ट FILE
को अपना स्वयं का म्यूटेक्स रखने की अनुमति है। इसका मतलब है कि एक थ्रेड fprintf(f1,...)
पर कॉल कर सकता है, उसी समय एक दूसरा थ्रेड fprintf(f2,...)
पर कॉल के बीच में होता है। यहां कोई दौड़ की स्थिति नहीं है। (क्या आपके libc वास्तव में समानांतर में उन दो कॉल चलाता है एक QoI मुद्दा है। मैं वास्तव में नहीं पता है glibc क्या करता है।)
इसी तरह, malloc
किसी भी आधुनिक प्रणाली में एक महत्वपूर्ण अनुभाग होने की संभावना नहीं है, क्योंकि आधुनिक प्रणालियों रहे हैं smart enough to keep one pool of memory for each thread in the system, सभी एन धागे एक पूल पर लड़ने के बजाय। (sbrk
सिस्टम कॉल अभी भी शायद एक महत्वपूर्ण अनुभाग होगा, लेकिन malloc
sbrk
में अपने समय की बहुत कम खर्च करता है। या mmap
, या जो कुछ भी शांत बच्चों को इन दिनों का उपयोग कर रहे हैं।)
ठीक है, तो क्या re-entrancy वास्तव में करता है क्या मतलब है? असल में, इसका मतलब है कि फ़ंक्शन को सुरक्षित रूप से रिकर्सली कहा जा सकता है - वर्तमान आमंत्रण "होल्ड ऑन होल्ड" होता है जबकि दूसरा आमंत्रण चलता है, और फिर पहला आविष्कार अभी भी "इसे छोड़कर कहां उठा सकता है" चुनने में सक्षम है। (तकनीकी रूप से यह एक रिकर्सिव कॉल के कारण नहीं हो सकता है: पहला आविष्कार थ्रेड ए में हो सकता है, जो थ्रेड बी द्वारा मध्य में बाधित हो जाता है, जो दूसरा आविष्कार करता है। लेकिन यह परिदृश्य धागा का एक विशेष मामला है -सुरक्षा, इसलिए हम इसके बारे में इस अनुच्छेद में
न तो printf
है और न ही malloc
भूल सकता है।) संभवतः हो सकता है किसी एकल थ्रेड द्वारा रिकर्सिवली कहा जाता है, क्योंकि वे पत्ती कार्य हैं (वे खुद को कॉल नहीं करते और न ही कॉल किसी भी उपयोगकर्ता द्वारा नियंत्रित कोड के लिए जो संभवतः एक रिकर्सिव कॉल कर सकता है)। और, जैसा कि हमने ऊपर देखा है, वे 2001 से * बहु-* थ्रेडेड पुनः-प्रवेश कॉल के खिलाफ थ्रेड-सुरक्षित रहे हैं (ताले का उपयोग करके)।
तो, जो भी आपको बताता है कि printf
और malloc
गैर-पुनर्विक्रेता गलत था; उनका कहना है कि उनके दोनों कार्यक्रमों में शायद महत्वपूर्ण वर्ग होने की संभावना है - बाधाएं जहां एक ही समय में केवल एक धागा हो सकता है।
पंडिताऊ ध्यान दें: glibc एक विस्तार है जिसके द्वारा printf
ही फिर से बुला सहित, मनमाने ढंग से उपयोगकर्ता कोड कॉल करने के लिए बनाया जा सकता है प्रदान करता है। यह अपने सभी क्रमपरिवर्तनों में पूरी तरह से सुरक्षित है - कम से कम जहां तक थ्रेड-सुरक्षा का संबंध है। (जाहिर है यह बिल्कुल पागल प्रारूप स्ट्रिंग कमजोरियों के लिए दरवाजा खुल जाता है।) दो वेरिएंट हैं: register_printf_function
(जो दस्तावेज और यथोचित समझदार है, लेकिन आधिकारिक तौर पर "पदावनत") और register_printf_specifier
(जो लगभग एक अतिरिक्त को छोड़ कर एक है अनियंत्रित पैरामीटर और total lack of user-facing documentation)। मैं उनमें से किसी एक की सिफारिश नहीं करता, और यहां केवल एक दिलचस्प तरफ के रूप में उनका उल्लेख करता हूं।
#include <stdio.h>
#include <printf.h> // glibc extension
int widget(FILE *fp, const struct printf_info *info, const void *const *args) {
static int count = 5;
int w = *((const int *) args[0]);
printf("boo!"); // direct recursive call
return fprintf(fp, --count ? "<%W>" : "<%d>", w); // indirect recursive call
}
int widget_arginfo(const struct printf_info *info, size_t n, int *argtypes) {
argtypes[0] = PA_INT;
return 1;
}
int main() {
register_printf_function('W', widget, widget_arginfo);
printf("|%W|\n", 42);
}
@ रिपुनजय-त्रिपाठी: printf, अगर यह सामान्य संसाधन को प्रिंट कर रहा है, उदा। stdio। malloc क्योंकि यह ताले पर भरोसा करते हैं। याद रखें कि पुनर्वित्तक और थ्रेड सुरक्षा के बीच एक अंतर है। जहां मॉलोक थ्रेड-सुरक्षित है। इस पोस्ट को देखो। http://stackoverflow.com/questions/855763/malloc-thread-safe। – yadab
आपको "कैननिकल कार्यान्वयन" कहां मिला? यदि डेवलपर्स कहते हैं कि वे हैं तो कार्य पुनर्वित्तक हैं। glibc डेवलपर्स नहीं कहते हैं कि malloc या printf पुनर्वित्तक हैं: तो वे नहीं हैं। – pmg
@pmg, "कैननिकल कार्यान्वयन" के बारे में शब्द मेरे द्वारा जोड़े गए थे। मेरा मतलब यही है। यह स्पष्ट है कि पुनर्वितरण एक कार्यान्वयन की संपत्ति है, इंटरफ़ेस की नहीं। हालांकि, उदाहरण के लिए, POSIX 'malloc' और 'printf' को पुनर्वित्त कार्यों के रूप में सूचीबद्ध नहीं करता है, और यह कारण के लिए है। इस quesiton में, ओपी जानना चाहता था कि कारण क्या है। –