मैं थ्रेड्स के बीच अपवादों का प्रचार कैसे कर सकता हूं?


105

हमारे पास एक फ़ंक्शन है जिसे एक सिंगल थ्रेड कहते हैं (हम इसे मुख्य थ्रेड नाम देते हैं)। फ़ंक्शन के शरीर के भीतर हम सीपीयू के गहन काम करने के लिए कई कार्यकर्ता थ्रेड्स को स्पॉन करते हैं, सभी थ्रेड्स समाप्त होने की प्रतीक्षा करते हैं, फिर मुख्य धागे पर परिणाम लौटाते हैं।

इसका परिणाम यह होता है कि कॉल करने वाला फ़ंक्शन का उपयोग भोलेपन से कर सकता है, और आंतरिक रूप से यह कई कोर का उपयोग करेगा।

अभी तक सभी अच्छे हैं ।।

हमारे पास जो समस्या है वह अपवादों से निपट रही है। हम अनुप्रयोग को क्रैश करने के लिए कार्यकर्ता थ्रेड पर अपवाद नहीं चाहते हैं। हम चाहते हैं कि फ़ंक्शन मुख्य थ्रेड पर उन्हें पकड़ने में सक्षम हो। हमें वर्कर थ्रेड्स पर अपवादों को पकड़ना होगा और उन्हें मुख्य थ्रेड में फैलाना होगा ताकि वे वहां से आगे बढ़ते रहें।

हम ऐसा कैसे कर सकते हैं?

सबसे अच्छा मैं सोच सकता हूँ:

  1. हमारे कार्यकर्ता धागे (std :: अपवाद और हमारे अपने कुछ) पर अपवादों की एक पूरी विविधता को पकड़ो।
  2. अपवाद के प्रकार और संदेश को रिकॉर्ड करें।
  3. मुख्य थ्रेड पर एक संबंधित स्विच स्टेटमेंट है, जो वर्कर थ्रेड पर दर्ज किए गए किसी भी प्रकार के अपवादों को पुनर्परिभाषित करता है।

इसमें अपवाद प्रकारों के सीमित सेट का समर्थन करने का स्पष्ट नुकसान है, और जब भी नए अपवाद प्रकार जोड़े जाते हैं तो उन्हें संशोधन की आवश्यकता होगी।

जवाबों:


89

C ++ 11 ने उस exception_ptrप्रकार को पेश किया जो थ्रेड्स के बीच अपवादों को परिवहन करने की अनुमति देता है:

#include<iostream>
#include<thread>
#include<exception>
#include<stdexcept>

static std::exception_ptr teptr = nullptr;

void f()
{
    try
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        throw std::runtime_error("To be passed between threads");
    }
    catch(...)
    {
        teptr = std::current_exception();
    }
}

int main(int argc, char **argv)
{
    std::thread mythread(f);
    mythread.join();

    if (teptr) {
        try{
            std::rethrow_exception(teptr);
        }
        catch(const std::exception &ex)
        {
            std::cerr << "Thread exited with exception: " << ex.what() << "\n";
        }
    }

    return 0;
}

क्योंकि आपके मामले में आपके पास कई कार्यकर्ता सूत्र हैं, आपको उनमें exception_ptrसे प्रत्येक के लिए एक रखने की आवश्यकता होगी ।

ध्यान दें कि exception_ptrएक साझा ptr-like सूचक है, इसलिए आपको exception_ptrप्रत्येक अपवाद को कम से कम एक इंगित करने की आवश्यकता होगी या उन्हें जारी किया जाएगा।

Microsoft विशिष्ट: यदि आप SEH अपवाद ( /EHa) का उपयोग करते हैं , तो उदाहरण कोड एक्सेस उल्लंघन जैसे SEH अपवादों को भी ले जाएगा, जो आप नहीं चाहते हैं।


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

75

वर्तमान में, एकमात्र पोर्टेबल तरीका सभी प्रकार के अपवादों के लिए कैच क्लॉज लिखना है, जो आप थ्रेड्स के बीच ट्रांसफर करना चाहते हैं, उस कैच क्लॉज से कहीं न कहीं जानकारी संग्रहित करें और फिर बाद में इसका उपयोग एक अपवाद को हटाने के लिए करें। यह Boost.Exception द्वारा लिया गया दृष्टिकोण है ।

C ++ 0x में, आप एक अपवाद को पकड़ने में सक्षम होंगे catch(...)और फिर std::exception_ptrउपयोग करने की स्थिति में इसे संग्रहीत कर सकते हैं std::current_exception()। आप इसे बाद में उसी या एक अलग धागे से फिर से उखाड़ सकते हैं std::rethrow_exception()

यदि आप Microsoft Visual Studio 2005 या उसके बाद का उपयोग कर रहे हैं, तो बस :: थ्रेड C ++ 0x थ्रेड लाइब्रेरी का समर्थन करता है std::exception_ptr। (अस्वीकरण: यह मेरा उत्पाद है)।


7
यह अब C ++ 11 का हिस्सा है और MSVS 2010 द्वारा समर्थित है; msdn.microsoft.com/en-us/library/dd293602.aspx देखें ।
जोहान राडे

7
यह linux पर gcc 4.4+ द्वारा भी समर्थित है।
एंथनी विलियम्स

कूल, उपयोग के एक उदाहरण के लिए लिंक है: en.cppreference.com/w/cpp/error/exception_ptr
एलेक्सिस

11

यदि आप C ++ 11 का उपयोग कर रहे हैं, तो std::futureठीक वही कर सकते हैं जो आप देख रहे हैं: यह स्वचालित रूप से अपवादों को जाल कर सकता है जो इसे श्रमिक धागे के शीर्ष पर बना देता है, और उस बिंदु पर मूल धागे से गुजरता std::future::getहै बुलाया। (दृश्यों के पीछे, यह बिल्कुल @AnthonyWilliams के जवाब की तरह होता है; यह सिर्फ आपके लिए पहले से ही लागू है।)

नीचे की ओर यह है कि "के बारे में देखभाल करना बंद करो" कोई मानक तरीका नहीं है std::future; यहां तक ​​कि इसके विध्वंसक बस तब तक ब्लॉक रहेंगे जब तक कि कार्य पूरा नहीं हो जाता। [संपादित करें, २०१:: ब्लॉकिंग-डिस्ट्रक्टोर व्यवहार केवल छद्म-वायदा से लौटा एक मिसफिट है std::async, जिसे आपको कभी भी उपयोग नहीं करना चाहिए। सामान्य वायदा उनके विध्वंसक में ब्लॉक नहीं होता है। यदि आप उपयोग कर रहे हैं, लेकिन फिर भी आप कार्यों को "रद्द" नहीं कर सकते std::future: वादे को पूरा करने वाला कार्य पर्दे के पीछे चलता रहेगा, भले ही कोई भी उत्तर के लिए नहीं सुन रहा हो।] यहां एक खिलौना उदाहरण दिया गया है, जो स्पष्ट कर सकता है कि मैं क्या हूं। मतलब है:

#include <atomic>
#include <chrono>
#include <exception>
#include <future>
#include <thread>
#include <vector>
#include <stdio.h>

bool is_prime(int n)
{
    if (n == 1010) {
        puts("is_prime(1010) throws an exception");
        throw std::logic_error("1010");
    }
    /* We actually want this loop to run slowly, for demonstration purposes. */
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    for (int i=2; i < n; ++i) { if (n % i == 0) return false; }
    return (n >= 2);
}

int worker()
{
    static std::atomic<int> hundreds(0);
    const int start = 100 * hundreds++;
    const int end = start + 100;
    int sum = 0;
    for (int i=start; i < end; ++i) {
        if (is_prime(i)) { printf("%d is prime\n", i); sum += i; }
    }
    return sum;
}

int spawn_workers(int N)
{
    std::vector<std::future<int>> waitables;
    for (int i=0; i < N; ++i) {
        std::future<int> f = std::async(std::launch::async, worker);
        waitables.emplace_back(std::move(f));
    }

    int sum = 0;
    for (std::future<int> &f : waitables) {
        sum += f.get();  /* may throw an exception */
    }
    return sum;
    /* But watch out! When f.get() throws an exception, we still need
     * to unwind the stack, which means destructing "waitables" and each
     * of its elements. The destructor of each std::future will block
     * as if calling this->wait(). So in fact this may not do what you
     * really want. */
}

int main()
{
    try {
        int sum = spawn_workers(100);
        printf("sum is %d\n", sum);
    } catch (std::exception &e) {
        /* This line will be printed after all the prime-number output. */
        printf("Caught %s\n", e.what());
    }
}

मैं सिर्फ एक काम उदाहरण का उपयोग कर लिखने की कोशिश की std::threadऔर std::exception_ptr, लेकिन कुछ के साथ गलत हो रहा है std::exception_ptr(libc ++ का उपयोग करके) तो मैं वास्तव में अभी तक काम करने के लिए इसे प्राप्त नहीं किया है। :(

[संपादित करें, 2017:

int main() {
    std::exception_ptr e;
    std::thread t1([&e](){
        try {
            ::operator new(-1);
        } catch (...) {
            e = std::current_exception();
        }
    });
    t1.join();
    try {
        std::rethrow_exception(e);
    } catch (const std::bad_alloc&) {
        puts("Success!");
    }
}

मुझे नहीं पता कि 2013 में मैं क्या गलत कर रहा था, लेकिन मुझे यकीन है कि यह मेरी गलती थी।]


क्यों आप भविष्य बनाता है एक नामित करने के लिए नियत कर सकता fहै और फिर emplace_backयह? क्या आप ऐसा नहीं कर सकते waitables.push_back(std::async(…));या मैं कुछ देख रहा हूँ (यह संकलित है, सवाल यह है कि क्या यह लीक हो सकता है, लेकिन मैं नहीं देखता कि कैसे)?
कोनराड रूडोल्फ

1
इसके अलावा, क्या waitआईएनजी के बजाय वायदा निरस्त करके स्टैक को अंजाम देने का एक तरीका है ? लाइनों के साथ कुछ "जैसे ही नौकरियों में से एक विफल हो गया, दूसरों को कोई फर्क नहीं पड़ता"।
कोनराड रूडोल्फ

4 साल बाद, मेरा जवाब अच्छी तरह से वृद्ध नहीं हुआ है। :) रे "क्यों": मुझे लगता है कि यह सिर्फ स्पष्टता के लिए था (यह दिखाने के लिए कि asyncकिसी और चीज के बजाय भविष्य को लौटाता है)। पुन: "इसके अलावा, वहाँ है": नहीं std::future, लेकिन शॉन पेरेंट की बात को देखें "बेहतर कोड: कंजेंसी" या मेरे "फ्यूचर्स फ्रॉम स्क्रैच" को अलग-अलग तरीकों से लागू करने के लिए कि यदि आप शुरुआत के लिए पूरे एसटीएल को फिर से लिखना नहीं चाहते हैं। :) प्रमुख खोज शब्द "रद्द करना" है।
क्क्सप्लसोन जूल

आपके जवाब के लिए धन्यवाद। एक मिनट मिलने पर मैं निश्चित रूप से वार्ता पर एक नज़र डालूंगा।
कोनराड रुडोल्फ

1
अच्छा 2017 संपादित करें। के रूप में ही स्वीकार किए जाते हैं, लेकिन एक scoped अपवाद सूचक के साथ। मैं इसे सबसे ऊपर रखूंगा और शायद बाकी लोगों को भी छुड़ा लूं।
नाथन कूपर

6

आपको समस्या यह है कि आप कई अपवादों को प्राप्त कर सकते हैं, कई थ्रेड से, जैसा कि प्रत्येक विफल हो सकता है, शायद विभिन्न कारणों से।

मैं मान रहा हूं कि मुख्य सूत्र किसी तरह थ्रेड के लिए इंतजार कर रहा है ताकि परिणाम प्राप्त किया जा सके, या नियमित रूप से अन्य थ्रेड्स की प्रगति की जांच की जा सके, और साझा डेटा तक पहुंच सिंक्रनाइज़ हो।

सरल उपाय

सरल समाधान प्रत्येक थ्रेड में सभी अपवादों को पकड़ना होगा, उन्हें एक साझा चर (मुख्य थ्रेड में) में रिकॉर्ड करना होगा।

एक बार जब सभी धागे समाप्त हो जाते हैं, तो यह तय करें कि अपवादों का क्या करना है। इसका मतलब है कि अन्य सभी थ्रेड्स ने अपना प्रसंस्करण जारी रखा, जो शायद, वह नहीं है जो आप चाहते हैं।

जटिल समाधान

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

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

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

जब सभी थ्रेड ने गर्भपात किया, तो मुख्य धागा अपवाद को आवश्यकतानुसार संभाल सकता है।


4

एक धागे से फेंका गया अपवाद मूल धागे में मौजूद नहीं होगा। थ्रेड्स में अलग-अलग संदर्भ और ढेर होते हैं, और आमतौर पर माता-पिता के धागे को वहां रहने और बच्चों के खत्म होने की प्रतीक्षा करने की आवश्यकता नहीं होती है, ताकि यह उनके अपवादों को पकड़ सके। उस कैच के लिए कोड में कोई जगह नहीं है:

try
{
  start thread();
  wait_finish( thread );
}
catch(...)
{
  // will catch exceptions generated within start and wait, 
  // but not from the thread itself
}

आपको प्रत्येक थ्रेड के अंदर अपवादों को पकड़ने और मुख्य थ्रेड में थ्रेड्स से बाहर निकलने की स्थिति की व्याख्या करने की आवश्यकता होगी जो आपको किसी अपवाद की आवश्यकता हो।

BTW, एक थ्रेड में एक पकड़ के अनुपस्थिति में यह विशिष्ट है यदि स्टैक अनइंडिंग बिल्कुल भी किया जाएगा, यानी आपके स्वचालित चर के विध्वंसक को समाप्त होने से पहले भी नहीं बुलाया जा सकता है। कुछ कंपाइलर ऐसा करते हैं, लेकिन इसकी आवश्यकता नहीं है।


3

क्या आप वर्कर थ्रेड में अपवाद को क्रमबद्ध कर सकते हैं, उस थ्रेड को मुख्य थ्रेड में भेज सकते हैं, डिसेरिअलाइज़ कर सकते हैं और फिर से फेंक सकते हैं? मुझे उम्मीद है कि इसके लिए अपवादों को काम करने के लिए सभी को एक ही कक्षा (या फिर से स्विच स्टेटमेंट वाली चीज़ों के साथ कम से कम कक्षाओं का एक छोटा सा सेट) प्राप्त करना होगा। इसके अलावा, मुझे यकीन नहीं है कि वे धारावाहिक होंगे, मैं सिर्फ ज़ोर से सोच रहा हूं।


यदि दोनों धागे एक ही प्रक्रिया में हैं तो इसे क्रमबद्ध करने की आवश्यकता क्यों है?
नवाज

1
@ नवाज़ क्योंकि अपवाद संभावना में थ्रेड-लोकल वैरिएबल के संदर्भ हैं जो स्वचालित रूप से अन्य थ्रेड्स के लिए उपलब्ध नहीं हैं।
tvanfosson

2

वास्तव में, एक धागे से दूसरे तक अपवादों को प्रसारित करने का कोई अच्छा और सामान्य तरीका नहीं है।

यदि, जैसा कि होना चाहिए, आपके सभी अपवाद std :: अपवाद से प्राप्त होते हैं, तो आपके पास एक शीर्ष-स्तरीय सामान्य अपवाद हो सकता है जो किसी तरह अपवाद को मुख्य धागे में भेज देगा जहां इसे फिर से फेंक दिया जाएगा। समस्या यह है कि आप अपवाद के फेंक बिंदु को ढीला कर रहे हैं। आप शायद इस जानकारी को प्राप्त करने और इसे प्रसारित करने के लिए संकलक-निर्भर कोड लिख सकते हैं।

यदि आपके सभी अपवाद वंशानुगत std :: अपवाद नहीं हैं, तो आप परेशानी में हैं और आपको अपने थ्रेड में शीर्ष स्तर के बहुत सारे कैच लिखने होंगे ... लेकिन समाधान अभी भी पकड़ में है।


1

आपको कार्यकर्ता में सभी अपवादों के लिए एक जेनेरिक कैच (गैर-एसटीडी अपवाद सहित, एक्सेस उल्लंघन की तरह) करना होगा, और कार्यकर्ता थ्रेड से एक संदेश भेजना होगा (मुझे लगता है कि आपके पास किसी तरह का संदेश है?) नियंत्रित करने के लिए। थ्रेड, अपवाद के लिए एक लाइव पॉइंटर है, और अपवाद की एक प्रति बनाकर वहां फिर से फेंक दें। तब कार्यकर्ता मूल वस्तु को मुक्त कर सकता है और बाहर निकल सकता है।


1

Http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html देखें । चाइल्ड थ्रेड में शामिल होने के लिए आप जो भी फ़ंक्शन कहते हैं, उसका एक रैपर फ़ंक्शन लिखना भी संभव है, जो कि बच्चे के थ्रेड द्वारा उत्सर्जित किसी भी अपवाद को स्वचालित रूप से फिर से फेंकता है (बूस्ट :: रेथ्रो_एक्ससेप्शन का उपयोग करके)।

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