2013-08-20 13 views
16

निम्न उदाहरण N3650 से लिया पर विचार के लिए प्रस्ताव N3650 में पुन: प्रारंभ योग्य कार्यों के बारे में एक उदाहरण को समझना:सी ++ 1 वर्ष

int cnt = 0; 
do { 
    cnt = await streamR.read(512, buf); 
    if (cnt == 0) 
     break; 
    cnt = await streamW.write(cnt, buf); 
} while (cnt > 0); 

मैं शायद कुछ याद आ रही है, लेकिन अगर मैं async और await अच्छी तरह से समझ, क्या है

int cnt = 0; 
do { 
    cnt = streamR.read(512, buf).get(); 
    if (cnt == 0) 
     break; 
    cnt = streamW.write(cnt, buf).get(); 
} while (cnt > 0); 

जहां दोनों read().get() और write().get(): ऊपर के उदाहरण जब प्रभाव लेखन के बराबर हैं के साथ दो निर्माणों की उपयोगिता दिखाने में बिंदु कॉल तुल्यकालिक हैं?

+0

मुझे लगता है कि यह स्पष्ट हो सकता है अगर आप घोषणा और पुन: प्रारंभ योग्य समारोह के कॉल सहित पूर्ण उदाहरण के रूप में चाहते हैं। – dyp

+0

यहां विज़ुअल सी ++ नवंबर सीटीपी कार्यान्वयन का उपयोग करके एक उदाहरण दिया गया है: http://stackoverflow.com/questions/19309508/using-sqlite-winrt-from-ac-windows-store-app –

उत्तर

7

प्रतीक्षा कीवर्ड भविष्य में कॉल करने के बराबर नहीं है। आप और अधिक इस तरह इसे देख सकता है आप इस से शुरू लगता है:

future<T> complex_function() 
{ 
    do_some_stuff(); 
    future<Result> x = await some_async_operation(); 
    return do_some_other_stuff(x); 
} 

यह कार्यात्मक रूप में कम या ज्यादा के रूप में

future<T> complex_function() 
{ 
    do_some_stuff(); 
    return some_async_operation().then([=](future<Result> x) { 
     return do_some_other_stuff(x); 
    }); 
} 

नोट एक ही है और अधिक या कम, क्योंकि वहाँ कुछ संसाधन प्रबंधन कर रहे हैं प्रभाव, do_some_stuff में बनाए गए चर को do_some_other_stuff निष्पादित करने के लिए प्रतिलिपि नहीं बनाई जानी चाहिए जैसे लैम्ब्डा संस्करण करेगा।

दूसरा संस्करण यह स्पष्ट करता है कि आमंत्रण पर क्या होगा।

  1. do_some_stuff() तुल्यकालिक लागू किया जाएगा जब आप complex_function कहते हैं।
  2. some_async_operation को असीमित रूप से कहा जाता है और भविष्य में परिणाम मिलता है। सटीक पल जब यह ऑपरेशन निष्पादित होता है तो आपके वास्तविक एसिंक्रोनस कॉलिंग कार्यान्वयन पर निर्भर करता है, जब आप धागे का उपयोग करते हैं तो यह तत्काल हो सकता है, जब भी आप .get() को डिलीवर निष्पादन का उपयोग करते हैं तो यह कहा जा सकता है।
  3. हम तुरंत do_some_other_stuff निष्पादित नहीं करते हैं, बल्कि इसे चरण 2 में प्राप्त भविष्य के लिए चेन करते हैं। इसका मतलब यह है कि इसे some_async_operation के परिणाम के रूप में जल्द ही निष्पादित किया जा सकता है लेकिन पहले नहीं। इसके अलावा, निष्पादन का क्षण रनटाइम द्वारा निर्धारित किया जाता है। यदि कार्यान्वयन केवल then प्रस्ताव को लपेटता है, तो इसका मतलब है कि यह मूल भविष्य के निष्पादक/लॉन्च नीति (एन 3558 के अनुसार) का उत्तराधिकारी होगा।
  4. फ़ंक्शन अंतिम भविष्य देता है, जो अंतिम परिणाम का प्रतिनिधित्व करता है। ध्यान दें कि यह भविष्य की आवश्यकता है, क्योंकि फंक्शन बॉडी के हिस्से को असीमित रूप से निष्पादित किया जाता है।
3

मुझे लगता है कि विचार यह है कि streamR.read() और streamW.write() कॉल अतुल्यकालिक मैं/हे संचालन कर रहे हैं और वापस जाने के वायदा, जो स्वत: await भाव से इंतजार कर रहे थे पर कर रहे हैं।

तो समकक्ष सिंक्रोनस संस्करण को परिणाम प्राप्त करने के लिए future::get() पर कॉल करना होगा उदा।

int cnt = 0; 
do { 
    cnt = streamR.read(512, buf).get(); 
    if (cnt == 0) 
     break; 
    cnt = streamW.write(cnt, buf).get(); 
} while (cnt > 0); 

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

+0

"जो स्वचालित रूप से प्रतीक्षा अभिव्यक्तियों द्वारा प्रतीक्षा की जाती है । " यह मुझे थोड़ा उलझन में डाल दिया। क्या आपका मतलब है * स्वचालित रूप से * जैसे * */जब आवश्यक हो * (उदाहरण के लिए पुन: प्रारंभ करने योग्य फ़ंक्शन के 'भविष्य'' पर' .get() 'को कॉल करके)? – dyp

+0

मैंने कॉल के बाद get() को जोड़कर प्रश्न को और स्पष्ट कर दिया। धन्यवाद। – Martin

+3

@ डीईपी, मेरा मानना ​​है कि पुन: प्रारंभ करने वाला फ़ंक्शन निष्पादन को फिर से शुरू कर सकता है (किसी अन्य थ्रेड में) न केवल तभी जब '.get() 'को लौटाया जाता है। मेरा मतलब यह था कि पुन: प्रारंभ करने योग्य फ़ंक्शन का निष्पादन 'प्रतीक्षा' ऑपरेटर से आगे नहीं बढ़ सकता है जब तक कि वह अपने ऑपरेंड के लिए इंतजार नहीं कर लेता है। मैंने अपना जवाब संपादित कर लिया है, इसलिए मुझे उम्मीद है कि यह स्पष्ट है –

6

एक और पूरी उदाहरण (उम्मीद सही):

future<void> forwardMsgs(istream& streamR, ostream& streamW) async 
{ 
    char buf[512]; 
    int cnt = 0; 
    do { 
     cnt = await streamR.read(512, buf); 
     if (cnt == 0) 
      break; 
     cnt = await streamW.write(cnt, buf); 
    } while (cnt > 0); 
} 

future<void> fut = forwardMsgs(myStreamR, myStreamW); 

/* do something */ 

fut.get(); 

महत्वपूर्ण बिंदु (ड्राफ्ट से उद्धृत) है:

निलंबित करने के बाद, एक पुन: प्रारंभ योग्य समारोह शेड्यूलिंग तर्क के आधार पर फिर से शुरू किया जा सकता है रनटाइम के अंत में और अंततः अपने तर्क को पूरा कर देगा, जिस बिंदु पर यह एक रिटर्न स्टेटमेंट (स्पष्ट या निहित) निष्पादित करता है और प्लेसहोल्डर में फ़ंक्शन का परिणाम मान सेट करता है।

और:

एक पुन: प्रारंभ योग्य समारोह इसके निष्पादन के निलंबन निम्नलिखित शुरू करने के बाद एक और धागा पर निष्पादन जारी रख सकते हैं।

है यही कारण है, जो मूल रूप से धागा forwardMsgsकर सकते हैं निलंबन अंक में से किसी में वापसी का आह्वान किया। यदि ऐसा होता है, तो /* do something */ लाइन के दौरान, forwardMsgs के अंदर कोड पर निष्पादित किया जा सकता है, भले ही फ़ंक्शन को "तुल्यकालिक" कहा गया हो।


यह उदाहरण है पुन: प्रारंभ योग्य समारोह अलग धागे से क्रियान्वित किया जा सकता बहुत

future<void> fut = std::async(forwardMsgs, myStreamR, myStreamW); 

/* do something */ 

fut.get(); 

अंतर के समान है: एक अलग धागा प्रत्येक बहाली/निलंबन के बाद (पुन: प्रारंभ योग्य समारोह के) निष्पादन शुरू कर सकते हैं बिंदु।

+0

* एक पुन: प्रारंभ करने वाला फ़ंक्शन उसके निष्पादन के निलंबन के बाद फिर से शुरू होने के बाद किसी अन्य थ्रेड पर निष्पादन जारी रख सकता है। * यह संदिग्ध रूप से हास्केल/गो/जंग से कुछ की तरह दिखता है: भाषा रनटाइम अतुल्यकालिक IO बनाता है उपयोगकर्ता के दृष्टिकोण और मल्टीप्लेक्स निष्पादन धाराओं से समकालिक रूप से सिंक्रोनस देखें। –

+0

@MaththieuM। सिवाय इसके कि यह भाषा रनटाइम (जरूरी नहीं) है। आप इसे किसी भी प्रकार के एसिंक्रोनस शेड्यूलिंग सिस्टम के साथ उपयोग कर सकते हैं जो वायदा और वादे के शीर्ष पर बनाया गया है। – KillianDS

+0

@ किलियनडीएस: ठीक है, इसमें रनटाइम * का शेड्यूलिंग तर्क शामिल है, क्या इसे अनुकूलित करने का कोई तरीका है या यह कार्यान्वयन द्वारा प्रदान किया गया है? मैं यह सुझाव नहीं देना चाहता था कि यह आईओ तक ही सीमित था, बस मुझे लगता है कि यह सी ++ के लिए हरे रंग के धागे थे क्योंकि वे हास्केल/गो/जंग (और शायद अन्य भाषाओं) में मौजूद थे। –

2

यहां उदाहरण के समारोह का उपयोग नहीं करने के लिए की सही अनुवाद का इंतजार है:

struct Copy$StackFrame { 
    promise<void> $result; 
    input_stream& streamR; 
    output_stream& streamW; 
    int cnt; 
    char buf[512]; 
}; 
using Copy$StackPtr = std::shared_ptr<Copy$StackFrame>; 

future<void> Copy(input_stream& streamR, output_stream& streamW) { 
    Copy$StackPtr $stack{ new Copy$StackFrame{ {}, streamR, streamW, 0 } }; 
    future<int> f$1 = $stack->streamR.read(512, stack->buf); 
    f$1.then([$stack](future<int> f) { Copy$Cont1($stack, std::move(f)); }); 
    return $stack->$result.get_future(); 
} 

void Copy$Cont1(Copy$StackPtr $stack, future<int> f$1) { 
    try { 
    $stack->cnt = f$1.get(); 
    if ($stack->cnt == 0) { 
     // break; 
     $stack->$result.set_value(); 
     return; 
    } 
    future<int> f$2 = $stack->streamW.write($stack->cnt, $stack->buf); 
    f$2.then([$stack](future<int> f) { Copy$Cont2($stack, std::move(f)); }); 
    } catch (...) { 
    $stack->$result.set_exception(std::current_exception()); 
    } 
} 

void Copy$Cont2(Copy$StackPtr $stack, future<int> f$2) { 
    try { 
    $stack->cnt = f$2.get(); 
    // while (cnt > 0) 
    if (cnt <= 0) { 
     $stack->$result.set_value(); 
     return; 
    } 
    future<int> f$1 = $stack->streamR.read(512, stack->buf); 
    f$1.then([$stack](future<int> f) { Copy$Cont1($stack, std::move(f)); }); 
    } catch (...) { 
    $stack->$result.set_exception(std::current_exception()); 
    } 
} 

आप देख सकते हैं, संकलक यहाँ परिवर्तन काफी जटिल है। यहां मुख्य बिंदु यह है कि, get() संस्करण के विपरीत, मूल Copy जैसे ही पहला एसिंक कॉल किया गया है, उसका भविष्य लौटाता है।

0

मेरे पास इन दो कोड नमूने के बीच अंतर के अर्थ के साथ एक ही समस्या है। चलिए उन्हें और अधिक पूरा करने के लिए थोड़ा लिखते हैं।

// Having two functions 
    future<void> f (istream&streamR, ostream&streamW) async 
    { int cnt = 0; 
     do { 
      cnt = await streamR.read(512, buf); 
      if (cnt == 0) 
      break; 
      cnt = await streamW.write(cnt, buf); 
     } while (cnt > 0); 
    } 
    void g(istream&streamR, ostream&streamW) 
    { int cnt = 0; 
     do { 
      cnt = streamR.read(512, buf).get(); 
      if (cnt == 0) 
      break; 
      cnt = streamW.write(cnt, buf).get(); 
     } while (cnt > 0); 
    } 
    // what is the difference between 
    auto a = f(streamR, streamW); 
    // and 
    auto b = async(g, streamR, streamW); 

आपको अभी भी कम से कम तीन ढेर की आवश्यकता है। दोनों मामलों में मुख्य धागा अवरुद्ध नहीं है। क्या यह धारणा है कि भविष्य में <> कंपाइलर द्वारा अधिक कुशलता से इंतजार किया जाएगा: प्राप्त करें()? खैर, बिना किसी इंतजार के इस्तेमाल किया जा सकता है।

धन्यवाद एडम ज़िलिंस्की

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