2011-12-21 11 views
8

मैं सी/सी ++ में एक पॉज़िक्स संगत बहु थ्रेडेड सर्वर लिख रहा हूं जो बड़ी संख्या में स्वीकार करने, पढ़ने और लिखने में सक्षम होना चाहिए असीमित कनेक्शन। सर्वर में कई कार्यकर्ता धागे हैं जो कार्य करते हैं और कभी-कभी (और अप्रत्याशित रूप से) क्यू डेटा को सॉकेट में लिखा जाना है। डेटा कभी-कभी (और अप्रत्याशित रूप से) ग्राहकों द्वारा सॉकेट को लिखा जाता है, इसलिए सर्वर को अतुल्यकालिक रूप से भी पढ़ना चाहिए। ऐसा करने का एक स्पष्ट तरीका यह है कि प्रत्येक कनेक्शन को एक धागा दें जो उसकी सॉकेट से/पढ़ता है और लिखता है; यह बदसूरत है, हालांकि, प्रत्येक कनेक्शन लंबे समय तक जारी रह सकता है और सर्वर को कनेक्शन का ट्रैक रखने के लिए सौ या हजार धागे पकड़ना पड़ सकता है।एक शर्त (pthread_cond_wait) और एक सॉकेट परिवर्तन (चयन) पर एक साथ प्रतीक्षा कर रहे हैं

एक बेहतर दृष्टिकोण एक एकल धागा होना चाहिए जो चुनिंदा()/pselect() फ़ंक्शंस का उपयोग करके सभी संचारों को संभाला जाए। यानी, एक धागा किसी भी सॉकेट पर पठनीय होने की प्रतीक्षा करता है, फिर इनपुट को संसाधित करने के लिए एक नौकरी पैदा करता है जिसे इनपुट उपलब्ध होने पर अन्य धागे के पूल द्वारा संभाला जाएगा। जब भी अन्य कार्यकर्ता धागे कनेक्शन के लिए आउटपुट उत्पन्न करते हैं, तो यह कतारबद्ध हो जाता है, और संचार धागा उस सॉकेट को लिखने से पहले लिखने योग्य होने के लिए इंतजार कर रहा है।

समस्या यह है कि संचार थ्रेड सर्वर के कार्यकर्ता थ्रेड द्वारा कतारबद्ध होने पर चयन() या pselect() फ़ंक्शन में प्रतीक्षा कर रहा है। यह संभव है कि, यदि कोई इनपुट कई सेकंड या मिनट के लिए आता है, तो आउटपुट का एक कतारबद्ध हिस्सा संचार थ्रेड को चुनने के लिए इंतजार करेगा() आईएनजी। ऐसा नहीं होना चाहिए, हालांकि - डेटा जितनी जल्दी हो सके लिखा जाना चाहिए।

अभी मैं थ्रेड-सुरक्षित होने के लिए कुछ समाधान देखता हूं। एक संचार थ्रेड व्यस्त है-इनपुट पर प्रतीक्षा करें और सॉकेट की सूची अपडेट करें जो यह एक या दूसरे के दसवें लिखने के लिए इंतजार कर रहा है। यह इष्टतम नहीं है क्योंकि इसमें व्यस्त प्रतीक्षा शामिल है, लेकिन यह काम करेगा। एक और विकल्प pselect() का उपयोग करना है और जब भी नया आउटपुट कतारबद्ध किया जाता है, तो यूएसआर 1 सिग्नल (या कुछ समतुल्य) भेजना, जिससे संचार थ्रेड को सॉकेट की सूची अपडेट करने की इजाजत मिलती है, जो तुरंत लिखने योग्य स्थिति के लिए प्रतीक्षा कर रहा है। मैं बाद वाले को पसंद करता हूं, लेकिन फिर भी किसी शर्त के लिए सिग्नल का उपयोग करना नापसंद करता है (pthread_cond_t)। फिर भी एक और विकल्प फ़ाइल डिस्क्रिप्टरों की सूची में शामिल करना होगा, जिस पर चयन() इंतजार कर रहा है, एक डमी फ़ाइल जिसे हम एक बाइट लिखते हैं जब भी किसी सॉकेट को चुनने योग्य fd_set में चयन() के लिए जोड़ा जाना चाहिए; यह संचार सर्वर को जगाएगा क्योंकि उस विशेष डमी फ़ाइल को तब पठनीय किया जाएगा, इस प्रकार संचार थ्रेड को इसके लिखने योग्य fd_set को तुरंत अपडेट करने की अनुमति मिल जाएगी।

मुझे सहजता से लगता है कि दूसरा प्रोग्राम (सिग्नल के साथ) सर्वर प्रोग्राम करने का 'सबसे सही' तरीका है, लेकिन अगर कोई जानता है कि उपरोक्त में से कौन सा सबसे कुशल है, आम तौर पर बोल रहा है, चाहे उपर्युक्त में से कोई भी ऐसी दौड़ स्थितियों का कारण बनता है जिनके बारे में मुझे पता नहीं है, या यदि कोई इस समस्या के अधिक सामान्य समाधान के बारे में जानता है। जो मैं वास्तव में चाहता हूं वह एक pthread_cond_wait_and_select() फ़ंक्शन है जो कमेट थ्रेड को सॉकेट में बदलाव या किसी शर्त से सिग्नल पर इंतजार करने की अनुमति देता है।

अग्रिम धन्यवाद।

उत्तर

6

यह एक आम समस्या है।

अक्सर इस्तेमाल किया जाने वाला समाधान पाइप को कार्यकर्ता धागे से संचार I/O थ्रेड तक संचार तंत्र के रूप में रखना होता है। अपने कार्य को पूरा करने के बाद एक कार्यकर्ता थ्रेड पाइप में परिणाम के लिए सूचक लिखता है। I/O थ्रेड पाइप के पठन के अंत में अन्य सॉकेट और फ़ाइल डिस्क्रिप्टर के साथ इंतजार कर रहा है और एक बार जब पाइप पढ़ने के लिए तैयार हो जाता है तो यह उठता है, परिणाम के लिए पॉइंटर को पुनर्प्राप्त करता है और नतीजे को क्लाइंट कनेक्शन में परिणाम को धक्का देता है -अवरुद्ध मोड।

नोट, चूंकि पाइप PIPE_BUF पर कम या उसके बराबर पढ़ता है और लिखता है, पॉइंटर्स एक शॉट में लिखे और पढ़ते हैं। परमाणु गारंटी की वजह से एक भी पाइपर्स को एक ही पाइप में लिखने वाले कई कार्यकर्ता धागे भी हो सकते हैं।

3

आपका दूसरा दृष्टिकोण जाने का क्लीनर तरीका है। select या epoll जैसी चीजों को अपनी सूची में कस्टम ईवेंट शामिल करना पूरी तरह से सामान्य है। ऐसी घटनाओं को संभालने के लिए हम अपने वर्तमान प्रोजेक्ट पर यही करते हैं। हम आवधिक घटनाओं के लिए टाइमर (लिनक्स timerfd_create पर) का भी उपयोग करते हैं।

लिनक्स पर eventfd आपको इस उद्देश्य के लिए ऐसी मनमानी उपयोगकर्ता घटनाएं बनाने देता है - इस प्रकार मैं कहूंगा कि यह काफी स्वीकार्य अभ्यास है। POSIX के लिए केवल फ़ंक्शन, ठीक है, हम्म, शायद पाइप कमांड में से एक या socketpair मैंने भी देखा है।

व्यस्त मतदान एक अच्छा विकल्प नहीं है।सबसे पहले आप स्मृति स्कैन करेंगे जो अन्य धागे द्वारा उपयोग किया जाएगा, इस प्रकार सीपीयू मेमोरी विवाद पैदा करेगा। दूसरा, आपको हमेशा अपने select कॉल पर वापस जाना होगा जो बड़ी संख्या में सिस्टम कॉल और संदर्भ स्विच बनाएगा जो समग्र सिस्टम प्रदर्शन को नुकसान पहुंचाएगा।

3

दुर्भाग्यवश, ऐसा करने का सबसे अच्छा तरीका प्रत्येक प्लेटफॉर्म के लिए अलग है। ऐसा करने के लिए कैनोलिक, पोर्टेबल तरीका है कि आपका I/O थ्रेड ब्लॉक poll में हो। यदि आपको poll छोड़ने के लिए I/O थ्रेड प्राप्त करने की आवश्यकता है, तो आप pipe पर एक बाइट भेजते हैं जो थ्रेड मतदान कर रहा है। इससे थ्रेड को poll से तुरंत बाहर निकलने का कारण बन जाएगा।

लिनक्स पर, epoll सबसे अच्छा तरीका है। बीएसडी-व्युत्पन्न ऑपरेटिंग सिस्टम (ओएसएक्स सहित, मुझे लगता है) पर, kqueue। सोलारिस पर, यह /dev/poll होता था और अब कुछ और है जिसका नाम मैं भूल जाता हूं।

तुम बस libevent या Boost.Asio की तरह एक पुस्तकालय का उपयोग कर विचार कर सकते हैं। वे आपको समर्थन देने वाले प्रत्येक प्लेटफ़ॉर्म पर सर्वश्रेष्ठ I/O मॉडल देते हैं।

संबंधित मुद्दे