बूस्ट होने पर कन्फ्यूज्ड :: asio :: io_service रन मेथड ब्लॉक / अनब्लॉक


88

Boost.Asio के लिए कुल शुरुआत होने के नाते, मैं उलझन में हूं io_service::run()। अगर कोई इस विधि को ब्लॉक / अनब्लॉक करता है तो कोई मुझे समझा सकता है, मैं इसकी सराहना करूंगा। दस्तावेज में कहा गया है:

run()समारोह ब्लॉक जब तक सभी काम समाप्त हो गया है और वहाँ कोई और अधिक संचालकों भेजा जाना है, या जब तक io_serviceरोक दिया गया है।

एकाधिक थ्रेड्स थ्रेड का run()एक पूल स्थापित करने के लिए फ़ंक्शन को कॉल कर सकते हैं जिसमें से io_serviceहैंडलर निष्पादित हो सकते हैं। पूल में इंतजार कर रहे सभी धागे बराबर हैं और io_serviceहैंडलर आह्वान करने के लिए उनमें से किसी एक को चुन सकते हैं।

run()फ़ंक्शन से एक सामान्य निकास का तात्पर्य है कि io_serviceऑब्जेक्ट बंद हो गया है ( stopped()फ़ंक्शन सही लौटाता है)। इसके बाद कॉल करने के लिए run(), run_one(), poll()या poll_one()तुरंत वापस आ जाएगी जब तक कि वहाँ के अपने पूर्व-कॉल है reset()

निम्नलिखित कथन का क्या अर्थ है?

[...] कोई और अधिक हैंडलर भेजे जाने के लिए नहीं [...]


के व्यवहार को समझने की कोशिश करते हुए io_service::run(), मैं इस उदाहरण (उदाहरण 3 ए) के पार आया । इसके भीतर, मैं उस io_service->run()ब्लॉक का निरीक्षण करता हूं और कार्य आदेशों की प्रतीक्षा करता हूं ।

// WorkerThread invines io_service->run()
void WorkerThread(boost::shared_ptr<boost::asio::io_service> io_service);
void CalculateFib(size_t);

boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<boost::asio::io_service::work> work(
   new boost::asio::io_service::work(*io_service));

// ...

boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
  worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}

io_service->post( boost::bind(CalculateFib, 3));
io_service->post( boost::bind(CalculateFib, 4));
io_service->post( boost::bind(CalculateFib, 5));

work.reset();
worker_threads.join_all();

हालाँकि, मैं जिस कोड पर काम कर रहा था, उसमें क्लाइंट टीसीपी / आईपी और रन पद्धति को ब्लॉक करता है, जब तक कि डेटा एसिंक्रोनस रूप से प्राप्त न हो जाए।

typedef boost::asio::ip::tcp tcp;
boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));

// Connect to 127.0.0.1:9100.
tcp::resolver resolver(*io_service);
tcp::resolver::query query("127.0.0.1", 
                           boost::lexical_cast< std::string >(9100));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
socket->connect(endpoint_iterator->endpoint());

// Just blocks here until a message is received.
socket->async_receive(boost::asio::buffer(buf_client, 3000), 0,
                      ClientReceiveEvent);
io_service->run();

// Write response.
boost::system::error_code ignored_error;
std::cout << "Sending message \n";
boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error);

इसके बारे में कोई भी स्पष्टीकरण run()नीचे दिए गए दो उदाहरणों में इसके व्यवहार का वर्णन करता है।

जवाबों:


234

आधार

एक सरलीकृत उदाहरण से शुरू करें और संबंधित Boost.Asio टुकड़ों की जांच करें:

void handle_async_receive(...) { ... }
void print() { ... }

...  

boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);

...

io_service.post(&print);                             // 1
socket.connect(endpoint);                            // 2
socket.async_receive(buffer, &handle_async_receive); // 3
io_service.post(&print);                             // 4
io_service.run();                                    // 5

हैंडलर क्या है ?

एक हैंडलर एक कॉलबैक से ज्यादा कुछ नहीं है। उदाहरण कोड में, 3 हैंडलर हैं:

  • printहैंडलर (1)।
  • handle_async_receiveहैंडलर (3)।
  • printहैंडलर (4)।

भले ही एक ही print()फ़ंक्शन दो बार उपयोग किया जाता है, प्रत्येक उपयोग को अपना विशिष्ट पहचानकर्ता हैंडलर बनाने के लिए माना जाता है। हैंडलर कई आकार और आकारों में आ सकते हैं, जिनमें बुनियादी कार्यों से लेकर ऊपर से अधिक जटिल निर्माण जैसे कि लैंबडैस से उत्पन्न boost::bind()फंक्शनलर्स शामिल हैं। जटिलता के बावजूद, हैंडलर अभी भी कॉलबैक से ज्यादा कुछ नहीं है।

काम क्या है ?

कार्य कुछ प्रसंस्करण है जिसे Boost.Asio ने आवेदन कोड की ओर से करने का अनुरोध किया है। कभी-कभी Boost.Asio कुछ काम शुरू कर सकता है जैसे ही इसके बारे में बताया गया है, और अन्य समय में यह काम करने के लिए प्रतीक्षा कर सकता है। एक बार जब यह काम पूरा हो जाता है, तो Boost.Asio आपूर्ति किए गए हैंडलर को आवेदन करके सूचित करेगा ।

Boost.Asio गारंटी देता है कि संचालकों केवल एक धागा है कि वर्तमान में बुला रहा है के भीतर चलेंगे run(), run_one(), poll(), या poll_one()। ये धागे हैं जो काम करेंगे और हैंडलर को बुलाएंगे । इसलिए, उपरोक्त उदाहरण में, print()इसे io_service(1) में पोस्ट किए जाने पर इनवोक नहीं किया जाता है । इसके बजाय, इसे जोड़ा जाता है io_serviceऔर बाद में समय पर लागू किया जाएगा। इस मामले में, यह io_service.run()(5) के भीतर है ।

अतुल्यकालिक संचालन क्या हैं?

एक एसिंक्रोनस ऑपरेशन कार्य बनाता है और Boost.Asio एक हैंडलर को आवेदन को सूचित करने के लिए आमंत्रित करेगा जब काम पूरा हो गया है। अतुल्यकालिक संचालन एक फ़ंक्शन को कॉल करके बनाए जाते हैं, जिसमें उपसर्ग के साथ एक नाम होता है async_। इन कार्यों को आरंभ करने वाले कार्यों के रूप में भी जाना जाता है

अतुल्यकालिक संचालन तीन अद्वितीय चरणों में विघटित हो सकते हैं:

  • आरंभ करने या सूचित करने के लिए, संबंधित io_serviceजो काम करता है उसे करने की आवश्यकता है। async_receiveआपरेशन (3) को सूचित io_serviceहै कि यह सॉकेट से एसिंक्रोनस रूप से पढ़ने के लिए डेटा की आवश्यकता होगी, तो async_receiveतुरंत वापस आती है।
  • वास्तविक कार्य करना। इस स्थिति में, जब socketडेटा प्राप्त होता है, तो बाइट्स को पढ़ा और कॉपी किया जाएगा buffer। वास्तविक काम या तो किया जाएगा:
    • दीक्षा समारोह (3), अगर Boost.Asio यह निर्धारित कर सकता है कि यह ब्लॉक नहीं होगा।
    • जब आवेदन स्पष्ट रूप से io_service(5) चलाते हैं ।
  • handle_async_receive ReadHandler को आमंत्रित करना । एक बार फिर, हैंडलर्स को केवल थ्रेड चलाने के भीतर लगाया जाता है io_service। इस प्रकार, जब भी काम किया जाता है (3 या 5), यह गारंटी है कि handle_async_receive()केवल io_service.run()(5) के भीतर लागू किया जाएगा ।

इन तीन चरणों के बीच समय और स्थान में अलगाव को नियंत्रण प्रवाह व्युत्क्रम के रूप में जाना जाता है। यह उन जटिलताओं में से एक है जो अतुल्यकालिक प्रोग्रामिंग को कठिन बनाता है। हालांकि, ऐसी तकनीकें हैं जो इसे कम करने में मदद कर सकती हैं, जैसे कि कोरटाइन का उपयोग करके ।

क्या करता है io_service.run()?

जब एक थ्रेड कॉल होता है io_service.run(), तो इस थ्रेड के भीतर से काम और हैंडलर मंगाए जाएंगे। उपरोक्त उदाहरण में, io_service.run()(5) तब तक ब्लॉक रहेगा जब तक:

  • यह दोनों printहैंडलर से मंगवाया गया है और वापस आ गया है , प्राप्त करने वाला ऑपरेशन सफलता या विफलता के साथ पूरा होता है, और इसके handle_async_receiveहैंडलर को वापस भेज दिया जाता है।
  • के io_serviceमाध्यम से स्पष्ट रूप से रोका जाता है io_service::stop()
  • एक अपवाद को एक हैंडलर के भीतर से फेंक दिया जाता है।

एक संभावित पीडो-ईश प्रवाह को निम्नलिखित के रूप में वर्णित किया जा सकता है:

io_service बनाएं
सॉकेट बनाएं
प्रिंट हैंडलर को io_service (1) में जोड़ें
कनेक्ट करने के लिए सॉकेट की प्रतीक्षा करें (2)
io_service (3) में एक अतुल्यकालिक रीड वर्क अनुरोध जोड़ें
io_service (4) के लिए प्रिंट हैंडलर जोड़ें
io_service चलाएं (5)
  वहाँ काम या हैंडलर है?
    हाँ, 1 काम और 2 हैंडलर है
      सॉकेट में डेटा है? नहीं, कुछ मत करो
      रन प्रिंट हैंडलर (1)
  वहाँ काम या हैंडलर है?
    हां, 1 काम और 1 हैंडलर है
      सॉकेट में डेटा है? नहीं, कुछ मत करो
      रन प्रिंट हैंडलर (4)
  वहाँ काम या हैंडलर है?
    हां, 1 काम है
      सॉकेट में डेटा है? नहीं, प्रतीक्षा जारी रखें
  - सॉकेट डेटा प्राप्त करता है -
      सॉकेट में डेटा है, इसे बफर में पढ़ें
      io_service में हैंडल_आसंक_प्रतिकार हैंडलर जोड़ें
  वहाँ काम या हैंडलर है?
    हां, 1 हैंडलर है
      रन हैंड_सुंक_सिव हैंडलर (3)
  वहाँ काम या हैंडलर है?
    नहीं, io_service को रोकें और वापस लौटें

ध्यान दें कि जब रीड समाप्त होता है, तो इसमें एक और हैंडलर जोड़ा जाता है io_service। यह सूक्ष्म विवरण अतुल्यकालिक प्रोग्रामिंग की एक महत्वपूर्ण विशेषता है। यह संचालकों को एक साथ जंजीर बनाने की अनुमति देता है । उदाहरण के लिए, यदि handle_async_receiveयह अपेक्षित सभी डेटा नहीं मिला, तो इसका कार्यान्वयन एक अन्य अतुल्यकालिक रीड ऑपरेशन को पोस्ट कर सकता है, जिसके परिणामस्वरूप io_serviceअधिक काम हो सकता है, और इस तरह से वापस नहीं लौटना होगा io_service.run()

ध्यान दें कि जब io_serviceकाम से बाहर चला गया है, तो इसे फिर से चलाने reset()से io_serviceपहले आवेदन करना होगा ।


उदाहरण प्रश्न और उदाहरण 3a कोड

अब, प्रश्न में संदर्भित कोड के दो टुकड़ों की जांच करने देता है।

प्रश्न संहिता

socket->async_receiveको काम जोड़ता है io_service। इस प्रकार, io_service->run()जब तक रीड ऑपरेशन सफलता या त्रुटि के साथ पूरा नहीं हो जाता है , तब तक ब्लॉक रहेगा, और ClientReceiveEventया तो दौड़ना समाप्त कर दिया है या एक अपवाद फेंकता है।

उदाहरण 3a कोड

यह समझने में आसान बनाने की उम्मीद में, यहां एक छोटा एनोटेट उदाहरण 3 ए है:

void CalculateFib(std::size_t n);

int main()
{
  boost::asio::io_service io_service;
  boost::optional<boost::asio::io_service::work> work =       // '. 1
      boost::in_place(boost::ref(io_service));                // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  work = boost::none;                                         // 4
  worker_threads.join_all();                                  // 5
}

उच्च-स्तर पर, प्रोग्राम 2 थ्रेड्स बनाएगा जो कि io_serviceईवेंट लूप (2) को प्रोसेस करेगा । इसका परिणाम एक साधारण थ्रेड पूल है जो फाइबोनैचि संख्या (3) की गणना करेगा।

प्रश्न कोड और इस कोड के बीच एक बड़ा अंतर यह है कि यह कोड वास्तविक कार्य से पहलेio_service::run() (2) आक्रमण करता है और हैंडलर को (3) में जोड़ा जाता है । तुरंत लौटने से रोकने के लिए , एक ऑब्जेक्ट (1) बनाया जाता है। यह ऑब्जेक्ट काम से बाहर जाने से रोकता है ; इसलिए, बिना काम के परिणाम के रूप में वापस नहीं आएगा।io_serviceio_service::run()io_service::workio_serviceio_service::run()

समग्र प्रवाह निम्नानुसार है:

  1. io_service::workइसमें जोड़े गए ऑब्जेक्ट को बनाएं और जोड़ें io_service
  2. थ्रेड पूल ने उस आक्रमण को बनाया io_service::run()। ये वर्कर थ्रेड ऑब्जेक्ट के io_serviceकारण से वापस नहीं आएंगे io_service::work
  3. 3 हैंडलर जोड़ें जो फाइबोनैचि संख्याओं की गणना करते हैं io_service, और तुरंत वापस लौटते हैं। श्रमिक सूत्र, मुख्य धागा नहीं, इन हैंडलर को तुरंत चलाना शुरू कर सकते हैं।
  4. हटाएँ io_service::workवस्तु।
  5. रनिंग खत्म करने के लिए वर्कर थ्रेड्स का इंतजार करें। यह केवल एक बार होगा जब सभी 3 हैंडलर ने निष्पादन समाप्त कर दिया है, क्योंकि io_serviceन तो हैंडलर हैं और न ही काम करते हैं।

कोड को अलग-अलग तरीके से लिखा जा सकता है, मूल कोड के समान, जहां हैंडलर जोड़े जाते हैं io_service, और फिर io_serviceइवेंट लूप संसाधित होता है। यह उपयोग करने की आवश्यकता को हटाता है io_service::work, और निम्न कोड में परिणाम:

int main()
{
  boost::asio::io_service io_service;

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'
  worker_threads.join_all();                                  // 5
}

सिंक्रोनस बनाम एसिंक्रोनस

यद्यपि प्रश्न में कोड एक अतुल्यकालिक ऑपरेशन का उपयोग कर रहा है, यह प्रभावी ढंग से तुल्यकालिक रूप से काम कर रहा है, क्योंकि यह अतुल्यकालिक ऑपरेशन पूरा होने की प्रतीक्षा कर रहा है:

socket.async_receive(buffer, handler)
io_service.run();

के बराबर है:

boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);

अंगूठे के एक सामान्य नियम के रूप में, सिंक्रोनस और एसिंक्रोनस ऑपरेशन को मिलाने से बचने की कोशिश करें। अक्सर बार, यह एक जटिल प्रणाली को एक जटिल प्रणाली में बदल सकता है। यह उत्तर अतुल्यकालिक प्रोग्रामिंग के लाभों पर प्रकाश डालता है, जिनमें से कुछ Boost.Asio प्रलेखन में भी शामिल हैं ।


13
बहुत बढ़िया पोस्ट। मैं सिर्फ एक चीज जोड़ना चाहूंगा क्योंकि मुझे लगता है कि यह पर्याप्त ध्यान नहीं देता है: चलाने के बाद () वापस लौटने से पहले आपको अपने io_service पर कॉल रीसेट () की आवश्यकता होती है, इसे फिर से चला सकते हैं। अन्यथा यह तुरंत लौट सकता है या नहीं async_ संचालन प्रतीक्षा कर रहा है या नहीं।
डेवेडर

बफर कहां से आता है? यह क्या है?
बर्बादी

मैं अभी भी उलझन में हूं। यदि मिक्सिंग सिंक है और एसिंक्स की सिफारिश नहीं की जाती है, तो शुद्ध एसिंक्स मोड क्या है? क्या आप io_service.run () के बिना कोड दिखाते हुए एक उदाहरण दे सकते हैं ?;
छप

@प्लाश एक io_service.poll()बकाया परिचालन पर रोक के बिना ईवेंट लूप को संसाधित करने के लिए उपयोग कर सकता है । सिंक्रोनस और एसिंक्रोनस ऑपरेशंस को मिक्स करने से बचने की प्राथमिक सिफारिश अनावश्यक जटिलता को जोड़ने से बचना है, और जब हैंडलर को पूरा होने में लंबा समय लगता है तो खराब जवाबदेही को रोकना है। कुछ मामले हैं जहां यह सुरक्षित है, जैसे कि जब कोई जानता है कि तुल्यकालिक ऑपरेशन अवरुद्ध नहीं होगा।
टान्नर सैंसबरी

"Boost.Asiorun() " में "फ़िलहाल" से आपका क्या मतलब है कि हैंडलर केवल उस थ्रेड के भीतर चलेंगे जो वर्तमान में कॉल कर रहा है .... " ? यदि एन थ्रेड्स हैं (जिसे कॉल किया गया है run()), तो कौन सा "चालू" धागा है? कई हो सकते हैं? या क्या आपका मतलब उस धागे से है जिसने async_*()(कहते हुए async_read) को अंजाम दिया है , साथ ही साथ अपने हैंडलर को बुलाने की गारंटी है?
नवाज़

18

यह कैसे सरल किया जाए run, इसे एक कर्मचारी के रूप में सोचें, जिसे कागज के ढेर को संसाधित करना होगा; यह एक शीट लेता है, जो शीट बताता है, वह शीट को फेंक देता है और अगले को ले जाता है; जब वह चादर से बाहर निकलता है, तो वह कार्यालय छोड़ देता है। प्रत्येक शीट पर किसी भी प्रकार का निर्देश हो सकता है, यहां तक ​​कि ढेर में एक नई शीट जोड़ना। एसियो पर वापस आप एक को दे सकते हैं io_service, अनिवार्य रूप से दो तरह से काम: का उपयोग करके postनमूना आप लिंक किए गए, के रूप में उस पर या अन्य वस्तुओं है कि आंतरिक रूप से फोन का उपयोग करके postपर io_service, की तरह socketऔर उसके async_*तरीकों।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.