Std :: वादा क्या है?


384

मैं C ++ 11 के साथ काफी परिचित हूं std::thread, std::asyncऔर std::futureघटक (जैसे इस उत्तर को देखते हैं ) है, जो कर रहे हैं सीधी-सपाट।

हालाँकि, मैं यह नहीं समझ सकता std::promiseकि यह क्या है, यह क्या करता है और किन स्थितियों में इसका उपयोग किया जाता है। मानक दस्तावेज़ में अपने वर्ग सिनॉप्सिस से परे पूरी जानकारी नहीं होती है, और न ही सिर्फ :: थ्रेड

क्या कोई ऐसी स्थिति का संक्षिप्त, संक्षिप्त उदाहरण दे सकता है, जहाँ std::promiseइसकी आवश्यकता है और जहाँ यह सबसे मुहावरेदार समाधान है?


2
यहाँ इसके साथ कुछ कोड है: en.cppreference.com/w/cpp/thread/future
chris

58
वास्तव में, वास्तव में लघु संस्करण है: std::promiseजहां std::futureसे आया है। std::futureवह है जो आपको एक ऐसे मूल्य को पुनः प्राप्त करने की अनुमति देता है जो आपसे वादा किया गया है। जब आप get()भविष्य पर कॉल करते हैं, तो यह तब तक इंतजार करता है जब तक कि जिस मालिक के std::promiseसाथ वह मूल्य निर्धारित करता है ( set_valueवादे पर कॉल करके )। यदि कोई मान सेट होने से पहले वादा नष्ट हो जाता है, और आप get()उस वादे से जुड़े भविष्य पर कॉल करते हैं, तो आपको एक std::broken_promiseअपवाद मिलेगा क्योंकि आपको एक मूल्य का वादा किया गया था, लेकिन आपके लिए एक प्राप्त करना असंभव है।
जेम्स मैकनेलिस

14
मेरा सुझाव है कि, यदि आप कर सकते हैं / चाहते हैं, तो एंथनी विलियम्स द्वारा एक्शन सी +
कंजरेन्सी

32
@KerrekSB std::broken_promiseमानक पुस्तकालय में सर्वश्रेष्ठ नामित पहचानकर्ता है। और नहीं है std::atomic_future
घनबी

3
डाउनवॉटर, अपनी आपत्ति समझाने की परवाह?
केरेक एसबी

जवाबों:


182

[Futures.state] के शब्दों में एक std::futureएक है अतुल्यकालिक वापसी वस्तु ( "एक वस्तु है कि एक साझा राज्य से परिणाम पढ़ता है") और एक std::promiseएक है अतुल्यकालिक प्रदाता ( "एक वस्तु है कि एक साझा राज्य के लिए एक परिणाम प्रदान करता है") यानी एक वादा वह चीज है जिसे आप एक परिणाम पर सेट करते हैं, ताकि आप इसे संबद्ध भविष्य से प्राप्त कर सकें

अतुल्यकालिक प्रदाता वह है जो शुरू में एक साझा राज्य बनाता है जिसे भविष्य संदर्भित करता है। std::promiseएक प्रकार का अतुल्यकालिक प्रदाता है, std::packaged_taskएक और है, और दूसरा आंतरिक विस्तार std::asyncहै। उनमें से प्रत्येक एक साझा राज्य बना सकते हैं और आपको std::futureउस राज्य को एक शेयर दे सकते हैं, और राज्य को तैयार कर सकते हैं।

std::asyncएक उच्च-स्तरीय सुविधा उपयोगिता है जो आपको एक अतुल्यकालिक परिणाम वस्तु प्रदान करती है और आंतरिक रूप से अतुल्यकालिक प्रदाता बनाने और कार्य पूरा होने पर साझा स्थिति तैयार करने का ख्याल रखती है। आप इसे std::packaged_task(या ) std::bindए और एक के साथ अनुकरण कर सकते हैं लेकिन यह सुरक्षित और उपयोग करने में आसान है ।std::promisestd::threadstd::async

std::promiseजब आप भविष्य के लिए एक अतुल्यकालिक परिणाम पास करना चाहते हैं, तो थोड़ा निम्न-स्तर है, लेकिन परिणाम तैयार करने वाले कोड को पास करने के लिए उपयुक्त एकल फ़ंक्शन में नहीं लपेटा जा सकता है std::async। उदाहरण के लिए, आपके पास कई promiseएस और संबद्ध futureएस का एक सरणी हो सकता है और एक एकल धागा हो सकता है जो कई गणना करता है और प्रत्येक वादे पर एक परिणाम सेट करता है। asyncकेवल आपको एक ही परिणाम वापस करने की अनुमति देगा, कई को वापस करने के लिए आपको asyncकई बार कॉल करने की आवश्यकता होगी , जो संसाधनों को बर्बाद कर सकता है।


10
संसाधनों की बर्बादी हो सकती है? गलत हो सकता है, अगर उस कोड को समानांतर नहीं किया जा सकता है।
पिल्ला

"अतुल्यकालिक वापसी" और "साझा राज्य से परिणाम पढ़ता है" ज्यादातर ऑर्थोगोनल होते हैं, जो पहले वाक्य को थोड़ा भ्रमित करता है। क्या आपके कहने का मतलब है कि राज्य का बंटवारा भविष्य और वादे के बीच है? यदि ऐसा है, तो कृपया स्पष्ट रूप से शुरू से कहें।
ईनपोकलम

@einpoklum आपने अंतिम शब्द से पहले "अतुल्यकालिक रिटर्न ऑब्जेक्ट" पढ़ना क्यों बंद कर दिया? मैं मानक की शब्दावली उद्धृत कर रहा हूं। A futureएक अतुल्यकालिक रिटर्न ऑब्जेक्ट का एक ठोस उदाहरण है , जो एक वस्तु है जो एक परिणाम को पढ़ता है जो कि साझा राज्य के माध्यम से, अतुल्यकालिक रूप से वापस आ गया था। A promiseएक अतुल्यकालिक प्रदाता का एक ठोस उदाहरण है , जो एक वस्तु है जो साझा स्थिति के लिए एक मूल्य लिखता है, जिसे अतुल्यकालिक रूप से पढ़ा जा सकता है। मैंने जो लिखा उसका मतलब था।
जोनाथन वेकली

496

मैं अब स्थिति को थोड़ा बेहतर समझ रहा हूं (यहां जवाबों के कारण कोई छोटी राशि नहीं है!), इसलिए मैंने सोचा कि मैं अपने खुद के लेखन को थोड़ा जोड़ देता हूं।


C ++ 11 में दो अलग-अलग, हालांकि संबंधित अवधारणाएं हैं: अतुल्यकालिक गणना (एक फ़ंक्शन जिसे कहीं और कहा जाता है), और समवर्ती निष्पादन (एक धागा , कुछ जो समवर्ती रूप से काम करता है)। दोनों कुछ हद तक रूढ़िवादी अवधारणाएं हैं। अतुल्यकालिक गणना केवल फ़ंक्शन कॉल का एक अलग स्वाद है, जबकि एक थ्रेड निष्पादन निष्पादन है। थ्रेड्स अपने आप में उपयोगी हैं, लेकिन इस चर्चा के उद्देश्य के लिए, मैं उन्हें कार्यान्वयन विवरण के रूप में मानूंगा।


अतुल्यकालिक गणना के लिए अमूर्तता का एक पदानुक्रम है। उदाहरण के लिए, मान लें कि हमारे पास एक फ़ंक्शन है जो कुछ तर्क देता है:

int foo(double, char, bool);

सबसे पहले, हमारे पास टेम्पलेट है std::future<T>, जो भविष्य के प्रकार का प्रतिनिधित्व करता है T। मान को सदस्य फ़ंक्शन के माध्यम से प्राप्त किया जा सकता है get(), जो परिणाम की प्रतीक्षा करके कार्यक्रम को प्रभावी ढंग से सिंक्रनाइज़ करता है। वैकल्पिक रूप से, एक भविष्य समर्थन करता है wait_for(), जिसका उपयोग यह जांचने के लिए किया जा सकता है कि परिणाम पहले से ही उपलब्ध है या नहीं। वायदा को साधारण रिटर्न प्रकारों के लिए अतुल्यकालिक ड्रॉप-इन प्रतिस्थापन के रूप में सोचा जाना चाहिए। हमारे उदाहरण समारोह के लिए, हम उम्मीद करते हैं किstd::future<int>

अब, पदानुक्रम पर, उच्चतम से निम्नतम स्तर तक:

  1. std::async: अतुल्यकालिक गणना करने के लिए सबसे सुविधाजनक और सीधा-सीधा तरीका asyncफ़ंक्शन टेम्पलेट के माध्यम से है, जो मिलान वाले भविष्य को लौटाता है:

    auto fut = std::async(foo, 1.5, 'x', false);  // is a std::future<int>

    विवरण पर हमारा बहुत कम नियंत्रण है। विशेष रूप से, हम यह भी नहीं जानते हैं कि फ़ंक्शन को समवर्ती, क्रमिक रूप से get(), या किसी अन्य काले जादू द्वारा निष्पादित किया जाता है । हालाँकि, आवश्यकता पड़ने पर परिणाम आसानी से प्राप्त होता है:

    auto res = fut.get();  // is an int
  2. अब हम इस बात पर विचार कर सकते हैं कि किसी चीज़ को कैसे लागू किया जाएasync , लेकिन एक ऐसे फैशन में जिसे हम नियंत्रित करते हैं। उदाहरण के लिए, हम आग्रह कर सकते हैं कि फ़ंक्शन को एक अलग थ्रेड में निष्पादित किया जाए। हम पहले से ही जानते हैं कि हम एक अलग धागा प्रदान कर सकते हैंstd::thread कक्षा के ।

    अमूर्तता का अगला निचला स्तर ठीक यही करता है std::packaged_task:। यह एक टेम्प्लेट है जो एक फ़ंक्शन को लपेटता है और फ़ंक्शंस रिटर्न वैल्यू के लिए भविष्य प्रदान करता है, लेकिन ऑब्जेक्ट स्वयं कॉल करने योग्य है, और इसे कॉल करना उपयोगकर्ता के विवेक पर है। हम इसे इस तरह सेट कर सकते हैं:

    std::packaged_task<int(double, char, bool)> tsk(foo);
    
    auto fut = tsk.get_future();    // is a std::future<int>

    एक बार जब हम कार्य को कॉल करते हैं और कॉल पूर्ण हो जाता है तो भविष्य तैयार हो जाता है। यह एक अलग धागे के लिए आदर्श काम है। हम सिर्फ करने के लिए सुनिश्चित करना है के लिए कदम थ्रेड में कार्य:

    std::thread thr(std::move(tsk), 1.5, 'x', false);

    धागा तुरंत चलने लगता है। हम या तो detachइसे कर सकते हैं , या joinइसके दायरे के अंत में हो सकते हैं, या जब भी (जैसे एंथनी विलियम्स के scoped_threadरैपर का उपयोग कर सकते हैं , जो वास्तव में मानक पुस्तकालय में होना चाहिए)। उपयोग के विवरण std::threadहमें यहाँ चिंता नहीं करते, हालाँकि; बस thrअंत में शामिल होना या अलग होना सुनिश्चित करें । क्या मायने रखता है कि जब भी फ़ंक्शन कॉल समाप्त होता है, हमारा परिणाम तैयार होता है:

    auto res = fut.get();  // as before
  3. अब हम सबसे निचले स्तर पर हैं: हम पैक किए गए कार्य को कैसे लागू करेंगे ? यह वह जगह है जहां std::promiseआने का वादा किया गया है। यह वादा भविष्य के साथ संवाद करने के लिए बिल्डिंग ब्लॉक है। प्रमुख चरण ये हैं:

    • कॉलिंग थ्रेड एक वादा करता है।

    • कॉलिंग थ्रेड वादा से एक भविष्य प्राप्त करता है।

    • फ़ंक्शन तर्कों के साथ वादा, एक अलग थ्रेड में ले जाया जाता है।

    • नया थ्रेड फ़ंक्शन निष्पादित करता है और वादा पूरा करता है।

    • मूल धागा परिणाम को पुनः प्राप्त करता है।

    एक उदाहरण के रूप में, यहाँ हमारे अपने "पैक किए गए कार्य" हैं:

    template <typename> class my_task;
    
    template <typename R, typename ...Args>
    class my_task<R(Args...)>
    {
        std::function<R(Args...)> fn;
        std::promise<R> pr;             // the promise of the result
    public:
        template <typename ...Ts>
        explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
    
        template <typename ...Ts>
        void operator()(Ts &&... ts)
        {
            pr.set_value(fn(std::forward<Ts>(ts)...));  // fulfill the promise
        }
    
        std::future<R> get_future() { return pr.get_future(); }
    
        // disable copy, default move
    };

    इस टेम्पलेट का उपयोग अनिवार्य रूप से उसी के समान है std::packaged_task। ध्यान दें कि पूरे कार्य को आगे बढ़ाते हुए वादे को पूरा करते हैं। अधिक तदर्थ स्थितियों में, कोई भी नए ऑब्जेक्ट में स्पष्ट रूप से एक वादा वस्तु को स्थानांतरित कर सकता है और इसे थ्रेड फ़ंक्शन का फ़ंक्शन तर्क बना सकता है, लेकिन ऊपर दिए गए कार्य की तरह एक आवरण अधिक लचीला और कम घुसपैठ समाधान जैसा लगता है।


अपवाद बनाना

वादे अपवादों से संबंधित हैं। अकेले एक वादे का इंटरफ़ेस पूरी तरह से अपने राज्य को व्यक्त करने के लिए पर्याप्त नहीं है, इसलिए अपवादों को फेंक दिया जाता है जब भी एक वादा पर एक ऑपरेशन का मतलब नहीं होता है। सभी अपवाद प्रकार के हैं std::future_error, जो कि व्युत्पन्न हैं std::logic_error। सबसे पहले, कुछ बाधाओं का वर्णन:

  • एक डिफ़ॉल्ट-निर्मित वादा निष्क्रिय है। निष्क्रिय वादे परिणाम के बिना मर सकते हैं।

  • भविष्य के माध्यम से प्राप्त होने पर एक वादा सक्रिय हो जाता है get_future()। हालाँकि, केवल एक भविष्य प्राप्त किया जा सकता है!

  • एक वादा के माध्यम से set_value()या तो संतुष्ट होना चाहिए या set_exception()अपने जीवनकाल समाप्त होने से पहले एक अपवाद निर्धारित किया है अगर उसका भविष्य भस्म होना है। एक संतुष्ट वादा परिणाम के बिना मर सकता है, और get()भविष्य पर उपलब्ध हो जाता है। अपवाद के साथ एक वादा get()भविष्य पर कॉल पर संग्रहीत अपवाद को बढ़ाएगा । यदि वादा न तो मूल्य और न ही अपवाद के साथ मर जाता है, get()तो भविष्य पर कॉल करने से "टूटा हुआ वादा" अपवाद बढ़ जाएगा।

इन विभिन्न असाधारण व्यवहारों को प्रदर्शित करने के लिए यहां थोड़ी परीक्षण श्रृंखला है। सबसे पहले, दोहन:

#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>

int test();

int main()
{
    try
    {
        return test();
    }
    catch (std::future_error const & e)
    {
        std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
    }
    catch (std::exception const & e)
    {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown exception." << std::endl;
    }
}

अब परीक्षणों पर।

केस 1: निष्क्रिय वादा

int test()
{
    std::promise<int> pr;
    return 0;
}
// fine, no problems

केस 2: सक्रिय वादा, अप्रयुक्त

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    return 0;
}
// fine, no problems; fut.get() would block indefinitely

केस 3: बहुत सारे वायदे

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();
    auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
    return 0;
}

केस 4: संतुष्ट वादा

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
    }

    return fut.get();
}
// Fine, returns "10".

केस 5: बहुत अधिक संतुष्टि

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // Error: "Promise already satisfied"
    }

    return fut.get();
}

एक ही अपवाद को फेंका जाता है यदि दोनों में से एक से अधिक है set_valueया set_exception

केस 6: अपवाद

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
    }

    return fut.get();
}
// throws the runtime_error exception

केस 7: टूटा हुआ वादा

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
    }   // Error: "broken promise"

    return fut.get();
}

आपने कहा "... जो प्रभावी रूप से परिणाम की प्रतीक्षा करके कार्यक्रम को सिंक्रनाइज़ करता है।" । यहाँ "सिंक्रोनाइज़" का क्या मतलब है? पूरे कथन का क्या अर्थ है? मैं यह समझने में असमर्थ हूं। इस शब्दकोश प्रविष्टि से "सिंक्रनाइज़" के अर्थ में से कोई भी मुझे वाक्य को समझने में मदद नहीं करता है। क्या सिर्फ "प्रतीक्षा" का अर्थ है "सिंक्रनाइज़ेशन"? क्या प्रत्येक प्रतीक्षा सिंक्रनाइज़ होती है? मुझे लगता है कि मैं आंशिक रूप से समझता हूं कि आपका क्या मतलब है, लेकिन मुझे यकीन नहीं है कि आप वास्तव में क्या मतलब है।
नवाज

9
अच्छा जवाब, आपकी मदद के लिए धन्यवाद। एसटीडी के भाग के बारे में :: async, मुझे याद है कि हम यह निर्धारित कर सकते हैं कि यह एक और धागा स्पॉन करेगा या ध्वज के साथ काम करेगा (std :: लॉन्च :: async, std :: लॉन्च :: deferred)
स्टीरियो

1
@ फेलिक्सडोमबेक: परफेक्ट फॉरवर्डिंग आदि std::functionके कई निर्माता हैं; का कोई कारण नहीं उन के उपभोक्ता को बेनकाब करने के लिए my_task
केरेक एसबी

1
@DaveedV: प्रतिक्रिया के लिए धन्यवाद! हां, यह परीक्षण का मामला 7 है: यदि आप या तो मूल्य या अपवाद निर्धारित किए बिना वादे को नष्ट करते हैं, तो get()भविष्य पर कॉल करना अपवाद को बढ़ाता है। मैं इसे "नष्ट होने से पहले" जोड़कर स्पष्ट करूंगा; कृपया मुझे बताएं कि क्या यह पर्याप्त रूप से स्पष्ट है।
केरेक एसबी

3
अंत में अपनी अद्भुत व्याख्या के धागे समर्थन पुस्तकालय grokking के got()मेरे ! futurepromise
सूरज चाँद

33

बार्टोज़ मिल्वेस्की एक अच्छा राइटअप प्रदान करता है।

सी ++ वायदा के कार्यान्वयन को छोटे ब्लॉकों के एक सेट में विभाजित करता है

std :: वादा इन हिस्सों में से एक है।

एक वादा एक फ़ंक्शन को थ्रेड को निष्पादित करने वाले थ्रेड से रिटर्न वैल्यू (या एक अपवाद) पारित करने के लिए एक वाहन है जो फ़ंक्शन भविष्य पर कैश करता है।

...

एक भविष्य वादा चैनल के प्राप्त अंत के आसपास निर्मित सिंक्रनाइज़ेशन ऑब्जेक्ट है।

इसलिए, यदि आप भविष्य का उपयोग करना चाहते हैं, तो आप एक वादे के साथ समाप्त होते हैं जिसका उपयोग आप अतुल्यकालिक प्रसंस्करण का परिणाम प्राप्त करने के लिए करते हैं।

पेज से एक उदाहरण है:

promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException

4
धागे के निर्माता में वादे को देखकर आखिरकार पैसा गिरा दिया। बार्टोज़ का लेख शायद सबसे बड़ा नहीं है, लेकिन यह बताता है कि तत्व एक साथ कैसे टाई करते हैं। धन्यवाद।
केरेक एसबी

28

एक मोटे अंदाज़ में आप इसके std::promiseदूसरे छोर के रूप में विचार कर सकते हैं std::future(यह गलत है , लेकिन चित्रण के लिए आप सोच सकते हैं जैसे कि यह था)। संचार चैनल के उपभोक्ता अंत का उपयोग करेगाstd::future साझा स्थिति से डेटा का उपभोग करने के लिए , जबकि निर्माता धागा std::promiseसाझा राज्य के लिए लिखने के लिए उपयोग करेगा ।


12
@KerrekSB: std::asyncवैचारिक रूप से (यह मानक द्वारा अनिवार्य नहीं है) एक फ़ंक्शन के रूप में समझा जाता है जो एक बनाता है std::promise, एक थ्रेड पूल में धकेलता है (प्रकार का, एक थ्रेड पूल हो सकता है, एक नया धागा हो सकता है ...) और रिटर्न std::futureकॉलर से जुड़ा है। ग्राहक की ओर आप std::futureऔर एक छोर पर इंतजार करेंगे दूसरे छोर पर परिणाम की गणना करेगा और इसे स्टोर करेगा std::promise। नोट: मानक को साझा स्थिति और इस विशेष उपयोग के मामले में std::futureअस्तित्व की आवश्यकता नहीं है std::promise
डेविड रॉड्रिग्ज़ - drieaseas

6
@KerrekSB: थ्रेड पर std::futureकॉल नहीं करेगा join, इसमें एक साझा राज्य के लिए एक पॉइंटर है जो वास्तविक संचार बफर है। साझा राज्य एक तुल्यकालन तंत्र (शायद है std::function+ std::condition_variableफोन करने वाले लॉक करने के लिए जब तक std::promiseपूरी हो जाती है। धागे के निष्पादन यह सब करने के लिए ओर्थोगोनल है, और कई कार्यान्वयन में आपको पता चल सकता है कि std::asyncनया सूत्र है कि तब शामिल हो गए हैं द्वारा निष्पादित नहीं कर रहे हैं, लेकिन बल्कि एक थ्रेड पूल द्वारा जिसका जीवनकाल कार्यक्रम के अंत तक फैला हुआ है।
डेविड रॉड्रिग्ज - dribeas

1
@ DavidRodríguez-dribeas: कृपया अपने उत्तर में टिप्पणियों से जानकारी संपादित करें।
मार्क मुत्ज़ -

2
@JonathanWakely: इसका मतलब यह नहीं है कि इसे एक नए धागे में निष्पादित किया जाना है, केवल यह कि इसे एसिंक्रोनस रूप से निष्पादित किया जाना है, जैसे कि यह एक नए बनाए गए धागे में चलाया गया था। इसका मुख्य लाभ std::asyncयह है कि रनटाइम लाइब्रेरी आपके लिए सही निर्णय ले सकती है ताकि थ्रेड्स की संख्या के संबंध में और ज्यादातर मामलों में मैं रनटाइम्स की अपेक्षा करूंगा जो थ्रेड पूल का उपयोग करते हैं। वर्तमान में VS2012 हुड के नीचे एक थ्रेड पूल का उपयोग करता है, और यह यथा- नियम का उल्लंघन नहीं करता है । ध्यान दें कि इस बात की बहुत कम गारंटी है कि इसे विशेष रूप से पूरा करने की आवश्यकता है ।
डेविड रॉड्रिग्ज - drieas

1
थ्रेड लोकल को फिर से इनिशियलाइज़ करने की ज़रूरत है, लेकिन जैसे-अगर नियम कुछ भी करने की अनुमति देता है (यही वजह है कि मैंने "
इटालिक्स

11

std::promiseasync फ़ंक्शन से दी जाने वाली जानकारी के लिए चैनल या मार्ग है। std::futureजब तक रिटर्न वैल्यू std::promiseतैयार नहीं हो जाती, तब तक सिंक्रोनाइज़ेशन मैकेनिज्म यानी कॉल करने वाले को इंतजार करना पड़ता है (मतलब इसका मूल्य फंक्शन के अंदर सेट किया गया है)।


8

अतुल्यकालिक प्रसंस्करण में वास्तव में 3 कोर इकाइयां हैं। वर्तमान में C ++ 11 उनमें से 2 पर केंद्रित है।

मूल बातें जो आपको कुछ तर्क को चलाने की आवश्यकता होती हैं, वे हैं:

  1. कार्य (तर्क कुछ functor वस्तु के रूप में पैक) कि 'कहीं' चलेंगे।
  2. वास्तविक प्रसंस्करण नोड - एक धागा, एक प्रक्रिया, आदि है कि इस तरह functors चलता है जब वे इसे करने के लिए प्रदान की जाती हैं। एक बेसिक वर्कर थ्रेड पूल यह कैसे करता है, इसके अच्छे विचार के लिए "कमांड" डिज़ाइन पैटर्न देखें।
  3. परिणाम संभाल : किसी ने कि परिणाम की जरूरत है, और एक वस्तु है कि यह उनके लिए मिल जाएगा की जरूरत है। ओओपी और अन्य कारणों से, इस हैंडल के एपीआई में किसी भी प्रतीक्षा या सिंक्रनाइज़ेशन को किया जाना चाहिए।

C ++ 11 उन चीजों को कहता है जो मैं (1) में बोलता हूं std::promise, और उन (3) में std::futurestd::thread(2) के लिए सार्वजनिक रूप से प्रदान की गई एकमात्र चीज़ है। यह दुर्भाग्यपूर्ण है क्योंकि वास्तविक कार्यक्रमों के लिए थ्रेड और मेमोरी संसाधनों का प्रबंधन करने की आवश्यकता होती है, और अधिकांश हर छोटे काम के लिए एक थ्रेड बनाने और नष्ट करने के बजाय थ्रेड पूल पर काम करना चाहते हैं (जो लगभग हमेशा अपने आप से अनावश्यक प्रदर्शन हिट का कारण बनता है और आसानी से संसाधन बना सकता है भुखमरी जो और भी बदतर है)।

हर्ब सटर और सी ++ 11 मस्तिष्क ट्रस्ट के अन्य लोगों के अनुसार, std::executorजावा में जैसे कि बहुत कुछ जोड़ने के लिए अस्थायी योजनाएं हैं- थ्रेड पूल के लिए आधार होगा और (2) के लिए तार्किक रूप से समान सेटअप होगा। हो सकता है कि हम इसे C ++ 2014 में देखेंगे, लेकिन मेरी शर्त C ++ 17 की तरह है (और अगर वे हमारे लिए मानक तैयार करते हैं तो भगवान हमारी मदद करेंगे)।


7

A std::promiseको एक वादा / भविष्य की जोड़ी के लिए एक अंतिम बिंदु के रूप में बनाया गया है और std::future(std से बनाया गया :: get_future()विधि का उपयोग करने का वादा ) दूसरा अंत बिंदु है। यह एक सरल, एक शॉट विधि है जो दो थ्रेड्स के लिए एक तरीका प्रदान करती है, जैसे कि एक थ्रेड एक संदेश के माध्यम से दूसरे थ्रेड को डेटा प्रदान करता है।

आप इसे सोच सकते हैं क्योंकि एक धागा डेटा प्रदान करने का वादा करता है और दूसरा धागा भविष्य में वादा करता है। इस तंत्र का उपयोग केवल एक बार किया जा सकता है।

वादा / भविष्य का तंत्र केवल एक दिशा है, थ्रेड से जो थ्रेड की set_value()विधि का std::promiseउपयोग करता है जो डेटा प्राप्त करने के लिए get()std::futureका उपयोग करता है । यदि अपवाद उत्पन्न होता हैget() भविष्य पद्धति को एक से अधिक बार कहा जाता है है।

यदि थ्रेड के साथ अपने वादे को पूरा करने के लिए std::promiseउपयोग नहीं किया set_value()गया है, तो जब दूसरा थ्रेड वादा को इकट्ठा करने के लिए कहता get()है std::future, तो दूसरा धागा एक प्रतीक्षा स्थिति में चला जाएगा जब तक कि पहले थ्रेड के साथ वादा पूरा नहीं होता है std::promiseजब वह set_value()विधि का उपयोग करता है डेटा भेजने के लिए।

तकनीकी विनिर्देश N4663 प्रोग्रामिंग भाषाओं के प्रस्तावित कोरआउट के साथ - कोरआउट के लिए C ++ एक्सटेंशन्स और विजुअल स्टूडियो 2017 C ++ कंपाइलर समर्थन co_await, का उपयोग करना std::futureऔर std::asyncकॉरआउट की कार्यक्षमता लिखना भी संभव है । में चर्चा और उदाहरण देखें https://stackoverflow.com/a/50753040/1466970 जो एक अनुभाग है कि के उपयोग पर चर्चा के रूप में है std::futureके साथ co_await

निम्न उदाहरण कोड, एक साधारण विज़ुअल स्टूडियो 2013 विंडोज कंसोल एप्लिकेशन, कुछ C ++ 11 संगामिति कक्षाओं / टेम्पलेट्स और अन्य कार्यक्षमता का उपयोग करके दिखाता है। यह वादा / भविष्य के लिए उपयोग को दिखाता है जो अच्छी तरह से काम करता है, स्वायत्त धागे जो कुछ कार्य करेंगे और रोकेंगे, और एक उपयोग जहां अधिक तुल्यकालिक व्यवहार की आवश्यकता होती है और कई सूचनाओं की आवश्यकता के कारण वादा / भविष्य की जोड़ी काम नहीं करती है।

इस उदाहरण के बारे में एक नोट विभिन्न स्थानों पर जोड़े गए विलंब हैं। इन देरी को केवल यह सुनिश्चित करने के लिए जोड़ा गया था कि कंसोल का उपयोग करके मुद्रित किए गए विभिन्न संदेश std::coutस्पष्ट होंगे और कई थ्रेड्स के पाठ को आपस में जोड़ा नहीं जाएगा।

के पहले भाग main()के लिए तीन अतिरिक्त धागे बनाने और उपयोग कर रहा है std::promiseऔर std::futureधागे के बीच डेटा भेजने के लिए। एक दिलचस्प बात यह है कि मुख्य धागा एक थ्रेड टी 2 शुरू करता है, जो मुख्य थ्रेड से डेटा की प्रतीक्षा करेगा, कुछ करें, और फिर तीसरे थ्रेड, T3 पर डेटा भेजें, जो तब कुछ करेगा और डेटा वापस भेजेगा। मुख्य सूत्र।

के दूसरे भाग main()दो धागे बनाता है और मुख्य थ्रेड से दो बनाया धागे से प्रत्येक के लिए एक से अधिक संदेशों अनुमति देने के लिए कतार का एक सेट। हम इसका उपयोग नहीं कर सकते std::promiseऔर इसके std::futureलिए क्योंकि वादा / भविष्य की जोड़ी एक शॉट है और इसे बार-बार उपयोग नहीं किया जा सकता है।

कक्षा के लिए स्रोत Sync_queueस्ट्रॉस्ट्रुप की द C ++ प्रोग्रामिंग भाषा: 4 वें संस्करण से है।

// cpp_threads.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <thread>  // std::thread is defined here
#include <future>  // std::future and std::promise defined here

#include <list>    // std::list which we use to build a message queue on.

static std::atomic<int> kount(1);       // this variable is used to provide an identifier for each thread started.

//------------------------------------------------
// create a simple queue to let us send notifications to some of our threads.
// a future and promise are one shot type of notifications.
// we use Sync_queue<> to have a queue between a producer thread and a consumer thread.
// this code taken from chapter 42 section 42.3.4
//   The C++ Programming Language, 4th Edition by Bjarne Stroustrup
//   copyright 2014 by Pearson Education, Inc.
template<typename Ttype>
class Sync_queue {
public:
    void  put(const Ttype &val);
    void  get(Ttype &val);

private:
    std::mutex mtx;                   // mutex used to synchronize queue access
    std::condition_variable cond;     // used for notifications when things are added to queue
    std::list <Ttype> q;              // list that is used as a message queue
};

template<typename Ttype>
void Sync_queue<Ttype>::put(const Ttype &val) {
    std::lock_guard <std::mutex> lck(mtx);
    q.push_back(val);
    cond.notify_one();
}

template<typename Ttype>
void Sync_queue<Ttype>::get(Ttype &val) {
    std::unique_lock<std::mutex> lck(mtx);
    cond.wait(lck, [this]{return  !q.empty(); });
    val = q.front();
    q.pop_front();
}
//------------------------------------------------


// thread function that starts up and gets its identifier and then
// waits for a promise to be filled by some other thread.
void func(std::promise<int> &jj) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();   // wait for the promise attached to the future
    std::cout << "  func " << myId << " future " << ll << std::endl;
}

// function takes a promise from one thread and creates a value to provide as a promise to another thread.
void func2(std::promise<int> &jj, std::promise<int>&pp) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();     // wait for the promise attached to the future

    auto promiseValue = ll * 100;   // create the value to provide as promised to the next thread in the chain
    pp.set_value(promiseValue);
    std::cout << "  func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl;
}

// thread function that starts up and waits for a series of notifications for work to do.
void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) {
    int myId = std::atomic_fetch_add(&kount, 1);

    int ll;
    q.get(ll);    // wait on a notification and when we get it, processes it.
    while (ll > 0) {
        std::cout << "  func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl;
        for (int i = iBegin; i < iEnd; i++) {
            pInts[i] = ll + i;
        }
        q.get(ll);  // we finished this job so now wait for the next one.
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::chrono::milliseconds myDur(1000);

    // create our various promise and future objects which we are going to use to synchronise our threads
    // create our three threads which are going to do some simple things.
    std::cout << "MAIN #1 - create our threads." << std::endl;

    // thread T1 is going to wait on a promised int
    std::promise<int> intPromiseT1;
    std::thread t1(func, std::ref(intPromiseT1));

    // thread T2 is going to wait on a promised int and then provide a promised int to thread T3
    std::promise<int> intPromiseT2;
    std::promise<int> intPromiseT3;

    std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3));

    // thread T3 is going to wait on a promised int and then provide a promised int to thread Main
    std::promise<int> intPromiseMain;
    std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain));

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2 - provide the value for promise #1" << std::endl;
    intPromiseT1.set_value(22);

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl;
    std::this_thread::sleep_for(myDur);
    intPromiseT2.set_value(1001);
    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl;

    std::future<int> intFutureMain(intPromiseMain.get_future());
    auto t3Promised = intFutureMain.get();
    std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl;

    t1.join();
    t2.join();
    t3.join();

    int iArray[100];

    Sync_queue<int> q1;    // notification queue for messages to thread t11
    Sync_queue<int> q2;    // notification queue for messages to thread t12

    std::thread t11(func3, std::ref(q1), 0, 5, iArray);     // start thread t11 with its queue and section of the array
    std::this_thread::sleep_for(myDur);
    std::thread t12(func3, std::ref(q2), 10, 15, iArray);   // start thread t12 with its queue and section of the array
    std::this_thread::sleep_for(myDur);

    // send a series of jobs to our threads by sending notification to each thread's queue.
    for (int i = 0; i < 5; i++) {
        std::cout << "MAIN #11 Loop to do array " << i << std::endl;
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q1.put(i + 100);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q2.put(i + 1000);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
    }

    // close down the job threads so that we can quit.
    q1.put(-1);    // indicate we are done with agreed upon out of range data value
    q2.put(-1);    // indicate we are done with agreed upon out of range data value

    t11.join();
    t12.join();
    return 0;
}

यह साधारण एप्लिकेशन निम्न आउटपुट बनाता है।

MAIN #1 - create our threads.
MAIN #2 - provide the value for promise #1
  func 1 future 22
MAIN #2.2 - provide the value for promise #2
  func2 2 promised 100100 ll was 1001
  func2 3 promised 10010000 ll was 100100
MAIN #2.4 - set_value 1001 completed.
MAIN #2.3 - intFutureMain.get() from T3. 10010000
MAIN #11 Loop to do array 0
  func3 4 start loop base 100 0 to 5
  func3 5 start loop base 1000 10 to 15
MAIN #11 Loop to do array 1
  func3 4 start loop base 101 0 to 5
  func3 5 start loop base 1001 10 to 15
MAIN #11 Loop to do array 2
  func3 4 start loop base 102 0 to 5
  func3 5 start loop base 1002 10 to 15
MAIN #11 Loop to do array 3
  func3 4 start loop base 103 0 to 5
  func3 5 start loop base 1003 10 to 15
MAIN #11 Loop to do array 4
  func3 4 start loop base 104 0 to 5
  func3 5 start loop base 1004 10 to 15

1

वादा तार का दूसरा छोर है।

कल्पना कीजिए कि आप एक futureद्वारा गणना की जा रही के मूल्य को पुनः प्राप्त करने की जरूरत है async। हालाँकि, आप नहीं चाहते हैं कि यह उसी धागे में गणना की जाए, और आप "अब" एक धागा भी नहीं रखते हैं - शायद आपके सॉफ्टवेयर को एक पूल से धागा लेने के लिए डिज़ाइन किया गया था, इसलिए आपको नहीं पता कि कौन करेगा अंत में चे गणना करें।

अब, आप इस (अभी तक अज्ञात) धागे / वर्ग / इकाई को क्या पास करते हैं? आप पास नहीं हैं future, क्योंकि यह परिणाम है । आप कुछ ऐसा पास करना चाहते हैं जो तार से जुड़ा हो futureऔर जो तार के दूसरे छोर का प्रतिनिधित्व करता हो , इसलिए आप केवल इस futureबारे में कोई ज्ञान नहीं देंगे कि वास्तव में कौन कुछ गणना करेगा / लिखेगा।

यह है promise। यह आपके से जुड़ा एक हैंडल हैfuture । तो futureएक है वक्ता , और साथ get()आप सुन जब तक कुछ ध्वनि बाहर आता है शुरू करते हैं, promiseएक है माइक्रोफोन ; लेकिन न सिर्फ किसी भी माइक्रोफोन, यह माइक्रोफोन वक्ता आप धारण करने के लिए एक भी तार के साथ जुड़ा हुआ है। आप जान सकते हैं कि दूसरे छोर पर कौन है, लेकिन आपको यह जानने की आवश्यकता नहीं है - आप बस इसे देते हैं और तब तक इंतजार करते हैं जब तक कि दूसरी पार्टी कुछ न कहे।


0

http://www.cplusplus.com/reference/future/promise/

एक वाक्य स्पष्टीकरण: फरहत :: मिल () इंतजार का वादा करता है :: set_value () हमेशा के लिए।

void print_int(std::future<int>& fut) {
    int x = fut.get(); // future would wait prom.set_value forever
    std::cout << "value: " << x << '\n';
}

int main()
{
    std::promise<int> prom;                      // create promise

    std::future<int> fut = prom.get_future();    // engagement with future

    std::thread th1(print_int, std::ref(fut));  // send future to new thread

    prom.set_value(10);                         // fulfill promise
                                                 // (synchronizes with getting the future)
    th1.join();
    return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.