2016-04-13 11 views
0

Dies ist ein relativ einfaches Programm, das Problem in meiner Anwendung reproduzieren:Wie vermeidet man es, shared_ptr im unangemessenen Thread-Kontext zu zerstören?

#include <boost/asio.hpp> 
#include <boost/asio/steady_timer.hpp> 
#include <boost/noncopyable.hpp> 
#include <boost/thread.hpp> 
#include <chrono> 
#include <functional> 
#include <iostream> 
#include <memory> 

class worker : boost::noncopyable { 
public: 
    explicit worker(boost::asio::io_service& io); 
    ~worker(); 
    void just_do_it(const std::function<void()>& when_done); 

private: 
    boost::asio::io_service& io_; 
    boost::asio::io_service worker_io_; 
    boost::thread thread_; 
}; 

worker::worker(boost::asio::io_service& io) 
: io_(io) 
{ 
    thread_ = boost::thread([this] { 
     boost::asio::io_service::work my_work(worker_io_); 
     worker_io_.run(); 
    }); 
} 

worker::~worker() 
{ 
    worker_io_.stop(); 
    std::clog << "join...\n"; 
    thread_.join(); 
} 

void worker::just_do_it(const std::function<void()>& when_done) 
{ 
    worker_io_.post([this, when_done] { 
     io_.post(when_done); 
     boost::asio::steady_timer(worker_io_, std::chrono::seconds(1)).wait(); 
    }); 
} 

int main() 
{ 
    boost::asio::io_service io; 
    boost::asio::steady_timer timer(io, std::chrono::seconds(5)); 
    timer.async_wait(std::bind([] { std::clog << "terminating...\n"; })); 
    { 
     auto my_worker = std::make_shared<worker>(io); 
     my_worker->just_do_it([my_worker] { 
      std::clog << "did it\n"; 
      my_worker->just_do_it([my_worker] { 
       std::clog << "did it second time\n"; 
       // now my_worker is not needed and we allow it to die 
      }); 
     }); 
    } 
    io.run(); 
} 

Wenn ich es auf Linux-Rechner laufen sehe ich:

did it 
did it second time 
join... 
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::thread_resource_error> >' 
    what(): boost thread: trying joining itself: Resource deadlock avoided 
Aborted 

es wegen Shared_ptr stürzt ruft Arbeiter destructor in Beschäftigter s Faden. Ich kann es so reparieren:

std::shared_ptr<worker> holder; 
{ 
    holder = std::make_shared<worker>(io); 
    holder->just_do_it([&holder] { 
     std::clog << "did it\n"; 
     holder->just_do_it([&holder] { 
      std::clog << "did it second time\n"; 
      // now worker is not needed and we destroy it 
      holder.reset(); 
     }); 
    }); 
} 

Aber es ist ein manuelles Objekt Lifetime Management. Es ist nicht zu viel besser als mit neuen und löschen. Gibt es eine Möglichkeit, es zu vermeiden?

+0

Sie möchten dem Thread später trotzdem beitreten, oder? Du brauchst also einen Griff zum Faden außerhalb davon. –

+0

Der Thread kann die Variablen der Arbeiterklassenmitglieder verwenden, deshalb muss ich ihn vor der Zerstörung des Arbeiters stoppen. –

+0

Ihre Lösung ist nicht weniger automatisch als Ihr problematischer Code, Sie haben sich einfach geändert, wenn der Zeiger zerstört wird (und ist immer noch besser als das manuelle 'neue' /' löschen' in Bezug auf Ausnahmen); Sie können eine andere Lösung finden, aber die, die Sie zur Verfügung stellen, scheint die einfachste IMO zu sein. – piwi

Antwort

1

Das Objekt thread sollte dem Code gehören, der die Lebensdauer des Threads verwaltet.

Zurzeit wird Ihre Lebensdauer thread von Ihrer Klasse worker verwaltet und Ihre Lebensdauer worker wird durch den Thread der Ausführung verwaltet.

Dies ist eine ziemlich grundlegende Verwaltungsschleife.

Die Semantik der Arbeiterzerstörung ist ebenfalls äußerst verwirrend. Sie haben Zeiger freigegeben, und wenn der letzte gemeinsame Zeiger zerstört wird, blockiert die Operation in irgendeinem Thread irgendwo eine unbekannte Aufgabe. Die Vorhersage, welcher Referenzzähloperationsblock unter diesem System nahezu unmöglich wird.


Ein Ansatz besteht darin, schwache Zeiger auf Arbeiter innerhalb von Arbeitern zu erfassen. Dann wird der Code, der für die Verwaltung der Lebensdauer des Worker-Threads zuständig ist, ausgewählt, wenn sie alle abgeschlossen sind. wenn der gemeinsame Zeiger weg ist.


Threads müssen im Allgemeinen von einer externen Entität verwaltet werden.

Ein Ansatz, der nützlich sein könnte, wäre ein Thread-Pool mit benannten Aufgabenwarteschlangen. Jede Aufgabenwarteschlange wird garantiert in der Reihenfolge ausgeführt, so dass sie wie ein Thread funktioniert, aber es gibt keine Garantie, dass die Aufgaben in der Warteschlange auf demselben Thread ausgeführt werden, noch gibt es eine Garantie, dass ein Thread im Leerlauf wartet neue Aufgaben in einer Warteschlange

Sie können entweder so etwas wie eine GUID verwenden, um Warteschlangen zu benennen, oder eine weniger globale eindeutige ID, die auf Anfrage generiert wird (wie ein Zeiger der Art ""). Im Fall der Sekunde entsorgt das Äquivalent zu Ihrem .join()-Aufruf Ihre Thread-Warteschlangen-ID.

Dies bewegt Sie weiter weg von der Verwendung von rohen boost Threading-Primitiven; Aber meiner Erfahrung nach sind die std-Grundformen eine Steigerung der Verwendung von Pthreads-Stil-Threads, sind aber immer noch weit von dem entfernt, was Sie eigentlich direkt im Client-Code verwenden möchten.