C ++ przerywa wywoływane bez aktywnego wyjątku

97

Otrzymuję błąd C ++ z wątkami:

terminate called without an active exception
Aborted

Oto kod:

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

template<typename TYPE>
class blocking_stream
{
public:
    blocking_stream(size_t max_buffer_size_)
        :   max_buffer_size(max_buffer_size_)   
    {
    }

    //PUSH data into the buffer
    blocking_stream &operator<<(TYPE &other)
    {
        std::unique_lock<std::mutex> mtx_lock(mtx); 
        while(buffer.size()>=max_buffer_size)
            stop_if_full.wait(mtx_lock);

        buffer.push(std::move(other));

        mtx_lock.unlock();
        stop_if_empty.notify_one();
        return *this;
    }
    //POP data out of the buffer 
    blocking_stream &operator>>(TYPE &other)
    {
        std::unique_lock<std::mutex> mtx_lock(mtx);
        while(buffer.empty())
            stop_if_empty.wait(mtx_lock);

        other.swap(buffer.front()); 
        buffer.pop();

        mtx_lock.unlock();
        stop_if_full.notify_one();
        return *this;
    }

private:
    size_t max_buffer_size;
    std::queue<TYPE> buffer;
    std::mutex mtx;
    std::condition_variable stop_if_empty,
                            stop_if_full;
    bool eof;   
};

Modelowałem swój kod na podstawie tego przykładu: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html

Co robię źle i jak naprawić błąd?

111111
źródło
9
Czy joinwszystkie wątki znajdują się w głównym programie?
Kerrek SB,
Pokaż nam resztę kodu.
Matt,
2
@Kerrek ah ha to rozwiązało problem, nie mam pojęcia dlaczego, chociaż jestem pewien, że główny wątek nie kończył się przed zakończeniem pracy pracowników. Czy moje alogorytmy blokujące również wyglądają dobrze?
111111
Skompilowany kod, który odtwarza problem, proszę.
Martin York,
3
Wygląda na to, że środowisko wykonawcze może w tym przypadku uzyskać lepszą diagnostykę?
Nemo

Odpowiedzi:

132

Kiedy obiekt wątku wychodzi poza zasięg i jest w stanie łączenia, program jest przerywany. Komitet Standardowy miał dwie inne opcje dla destruktora łączonego wątku. Może się po cichu dołączyć - ale połączenie może nigdy nie powrócić, jeśli wątek utknie. Lub może odłączyć nić (odłączonego wątku nie można łączyć). Jednak odłączone wątki są bardzo trudne, ponieważ mogą przetrwać do końca programu i zepsuć zwolnienie zasobów. Jeśli więc nie chcesz przerywać programu, upewnij się, że dołączasz (lub odłączasz) każdy wątek.

Bartosz Milewski
źródło
1
„Gdy obiekt wątku wychodzi poza zasięg i jest w stanie łączenia, program jest przerywany” Czy możesz podać martwy, prosty, odtwarzalny przykład? Przykład w PO jest nieco skomplikowany.
Alec Jacobson
1
I to stwierdzenie wydaje się sprzeczne z tą odpowiedzią: stackoverflow.com/a/3970921/148668
Alec Jacobson
6
@mangledorf: Zauważ, że mówią aobut boost :: thread i mówię o std :: thread. Te dwa mają różne zachowania destrukcyjne. To była świadoma decyzja Komitetu.
Bartosz Milewski
A jeśli napotkasz ten problem std::async? Jak dołączyć / odłączyć dowolny wątek, który może zostać tam utworzony? Wydaje się, że nie wystarczy czekać na wynikową przyszłość, ponieważ mówi, że wątek może „potencjalnie pochodzić z puli wątków”, a funkcja wait () w przyszłości nie oznacza tak naprawdę zakończenia wątku w puli (i to nie i tak nie ma sensu dla rozsądnych pul wątków).
Jason C,
2
Tylko aktualizacja, która w C ++ 20 std::jthreadwywoła .join()destruktor (ponieważ wychodzi poza zakres). Które osobiście bardziej mi się podoba, ponieważ lepiej podąża za RAII.
pooya
46

Jak odtworzyć ten błąd:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() { 
  std::thread t1(task1, "hello"); 
  return 0;
}

Skompiluj i uruchom:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
terminate called without an active exception
Aborted (core dumped)

Otrzymujesz ten błąd, ponieważ nie dołączyłeś ani nie odłączyłeś swojego wątku.

Jednym ze sposobów, aby to naprawić, dołącz do wątku w ten sposób:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() { 
  std::thread t1(task1, "hello"); 
  t1.join();
  return 0;
}

Następnie skompiluj i uruchom:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
task1 says: hello

Innym sposobem, aby to naprawić, odłącz go w ten sposób:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() 
{ 
     {

        std::thread t1(task1, "hello"); 
        t1.detach();

     } //thread handle is destroyed here, as goes out of scope!

     usleep(1000000); //wait so that hello can be printed.
}

Skompiluj i uruchom:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
task1 says: hello

Przeczytaj o odłączaniu wątków C ++ i dołączaniu do wątków C ++.

Eric Leschinski
źródło
1
w tym kontekście użycie usleep () ma sens tylko wtedy, gdy wątek jest odłączony, a uchwyt został zniszczony (przez wyjście poza zasięg). Więc zredagowałem twój kod, aby to odzwierciedlić.
Nawaz
To nie działa. Nadal pojawia się błąd, gdy naciskam ctrl + c, aby przerwać proces, co uniemożliwia ukończenie funkcji join ().
Cerin
Nie chcesz, aby zegar wymuszał wykonanie odłączonego wątku, użyj blokady.
Yvain
17

Eric Leschinski i Bartosz Milewski już udzielili odpowiedzi. Tutaj postaram się przedstawić to w bardziej przyjazny dla początkujących sposób.

Po uruchomieniu wątku w zakresie (który sam działa w wątku), należy jawnie upewnić się, że jedna z poniższych sytuacji nastąpi, zanim wątek wyjdzie poza zakres:

  • Środowisko wykonawcze wychodzi z zakresu dopiero po zakończeniu wykonywania tego wątku. Osiąga się to poprzez połączenie z tym wątkiem. Zwróć uwagę na język, to zewnętrzny zakres łączy się z tym wątkiem.
  • Środowisko wykonawcze pozostawia wątek do samodzielnego działania. Tak więc program wyjdzie z zakresu, niezależnie od tego, czy wątek zakończył wykonywanie, czy nie. Ten wątek jest wykonywany i zamykany samodzielnie. Osiąga się to poprzez odłączenie nici. Może to prowadzić do problemów, na przykład, jeśli wątek odwołuje się do zmiennych w tym zewnętrznym zakresie.

Zauważ, że zanim wątek zostanie przyłączony lub odłączony, może być dobrze zakończony. Nadal każda z tych dwóch operacji musi być wykonana jawnie.

Hari
źródło
1

Tak długo, jak twój program umiera, to bez odłączania lub łączenia wątku ten błąd wystąpi. Bez odłączania i łączenia wątku należy po utworzeniu wątku dawać nieskończoną pętlę.

int main(){

std::thread t(thread,1);

while(1){}

//t.detach();
return 0;}

Ciekawe jest również to, że po spaniu lub zapętleniu nić można odłączyć lub połączyć. Również w ten sposób nie otrzymasz tego błędu.

Poniższy przykład pokazuje również, że trzeci wątek nie może wykonać swojej pracy przed główną śmiercią. Ale ten błąd też nie może się zdarzyć, o ile odłączysz gdzieś w kodzie. Trzeci wątek zostanie uśpiony przez 8 sekund, ale główny umrze za 5 sekund.

void thread(int n) {std::this_thread::sleep_for (std::chrono::seconds(n));}

int main() {
std::cout << "Start main\n";
std::thread t(thread,1);
std::thread t2(thread,3);
std::thread t3(thread,8);
sleep(5);

t.detach();
t2.detach();
t3.detach();
return 0;}
user2908225
źródło
1

rok, wątek musi być join (). kiedy główne wyjście

yongyu wu
źródło
1
Ta odpowiedź prawdopodobnie bardziej pasuje do komentarza dołączonego do innej odpowiedzi. I muszę powiedzieć, witamy w Stack Overflow!
Contango
0

Najpierw definiujesz wątek. A jeśli nigdy nie wywołasz funkcji join () ani detach () przed wywołaniem destruktora wątków, program przerwie działanie.

Jak poniżej, wywołanie destruktora wątku bez uprzedniego wywoływania join (aby poczekać na jego zakończenie) lub detach jest gwarantowane natychmiastowego wywołania std :: terminate i zakończenia programu.

Niejawne odłączenie lub dołączenie do joinable () wątku w jego destruktorze może spowodować trudne do debugowania błędy poprawności (w przypadku odłączania) lub wydajności (w przypadku łączenia) napotykane tylko w przypadku zgłoszenia wyjątku. Dlatego programista musi zapewnić, że destruktor nigdy nie zostanie uruchomiony, gdy wątek jest nadal możliwy do dołączenia.

Li Yingjun
źródło