2012-06-06 8 views
20

Das folgende Beispiel wird erfolgreich ausgeführt (dh nicht hängen), wenn mit Clang 3.2 oder GCC 4.7 auf Ubuntu 12.04 kompiliert, aber hängt wenn ich mit VS11 Beta oder VS2012 RC kompiliere.std :: thread :: join() hängt, wenn nach main() aufgerufen wird bei Verwendung von VS2012 RC

#include <iostream> 
#include <string> 
#include <thread> 
#include "boost/thread/thread.hpp" 

void SleepFor(int ms) { 
    std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 
} 

template<typename T> 
class ThreadTest { 
public: 
    ThreadTest() : thread_([] { SleepFor(10); }) {} 
    ~ThreadTest() { 
    std::cout << "About to join\t" << id() << '\n'; 
    thread_.join(); 
    std::cout << "Joined\t\t" << id() << '\n'; 
    } 
private: 
    std::string id() const { return typeid(decltype(thread_)).name(); } 
    T thread_; 
}; 

int main() { 
    static ThreadTest<std::thread> std_test; 
    static ThreadTest<boost::thread> boost_test; 
// SleepFor(100); 
} 

Das Problem scheint zu sein, dass std::thread::join() nie zurück, wenn sie aufgerufen wird, nachdem main verlassen hat. Es ist bei WaitForSingleObject in _Thrd_join in cthread.c blockiert blockiert.

uncommenting SleepFor(100); am Ende der main ermöglicht das Programm korrekt zu verlassen, wie std_test nicht-statische ist machen. Mit boost::thread wird auch das Problem vermieden.

Also würde ich gerne wissen, ob ich hier undefiniertes Verhalten hier anruft (scheint mir unwahrscheinlich), oder wenn ich einen Bug gegen VS2012 einreichen sollte?

+0

Beta und Release Candidate Probleme? – Jaywalker

+0

@Jaywalker Nun, ich denke, das ist, was ich frage. Ist es vernünftig zu erwarten, dass 'join()' nicht hängt, wenn es aufgerufen wird, nachdem 'main' beendet wurde. Wenn ja, ist dies ein Fehler, den ich eher früher als später in MS Connect bekommen möchte. – Fraser

+0

Könnten Sie versuchen zu testen, ob die 'std :: thread :: joinable()' immer noch True vor dem Hängen zurückgibt? – Plexico

Antwort

22

durch Frasers Beispielcode in seinem connect-Bug (https://connect.microsoft.com/VisualStudio/feedback/details/747145) mit VS2012 RTM Tracing scheint einen ziemlich einfachen Fall von Deadlocks zu zeigen. Dies ist wahrscheinlich nicht spezifisch für std::thread - wahrscheinlich _beginthreadex leidet das gleiche Schicksal.

Was ich im Debugger sehe, ist die folgende:

Auf dem Haupt-Thread, die main() Funktion beendet hat, hat der Prozess Bereinigungscode einen kritischen Abschnitt erworben _EXIT_LOCK1 genannt, die so genannten destructor von ThreadTest, und wartet darauf, (unbegrenzt) auf dem zweiten Thread zu beenden (über den Anruf an join()).

Die anonyme Funktion des zweiten Threads ist abgeschlossen und befindet sich im Threadsäuberungscode, der darauf wartet, den kritischen Abschnitt _EXIT_LOCK1 zu übernehmen. Leider besitzt der Hauptthread aufgrund des Timings der Dinge (wobei die Lebensdauer des zweiten Threads anonyme Funktion die der Funktion main() überschreitet) diesen kritischen Abschnitt.

DEADLOCK.

Alles, was die Lebensdauer von main() verlängert, so dass der zweite Thread _EXIT_LOCK1 erwerben kann, bevor der Hauptthread die Deadlock-Situation vermeidet. Aus diesem Grund führt das Auskommentieren des Schlafes in main() zu einem sauberen Herunterfahren.

Alternativ, wenn Sie das statische Schlüsselwort aus dem ThreadTest lokalen Variable entfernen, wird der Destruktor Anruf an das Ende der main() Funktion (statt in dem Prozess Bereinigungscode) bewegt wird, die dann blockiert, bis der zweite Thread ist beendet - vermieden die Deadlock-Situation.

Oder Sie könnten eine Funktion zu ThreadTest hinzufügen, die join() aufruft und diese Funktion am Ende von main() aufrufen - wieder die Deadlock-Situation zu vermeiden.

+0

Schöne Analyse. ~ –

+0

@CWoods - können Sie den Link zu meinem MS Connect Fehlerbericht in Ihre Antwort stecken? Ich möchte deine als korrekt markieren und meine löschen. – Fraser

+0

@Frazer - Fertig! Ich bin froh, dass ich geholfen habe! – CWoods

1

Ich glaube, Ihre Threads wurden bereits beendet und ihre Ressourcen freigegeben nach der Beendigung Ihrer Hauptfunktion und vor der statischen Zerstörung. Dies ist das Verhalten der VC-Laufzeiten, die auf mindestens VC6 zurückgehen.

Do child threads exit when the parent thread terminates

boost thread and process cleanup on windows

+0

Diese Antwort ist spezifisch für Windows, aber ich nehme an, seit Sie VS verwenden. – TractorPulledPork

+0

Ich benutze Windows, aber ich bin mir nicht sicher, ob deine Antwort richtig ist. Der Hauptthread ist derjenige, der 'join' ausführt, also obwohl die Funktion' main' beendet wurde, wurde der Hauptthread nicht beendet. Danke trotzdem. – Fraser

+0

Relevanteren Inhalt: http://stackoverflow.com/questions/8001989/boost-thread-and-process-cleanup-on-windows – TractorPulledPork

-1

Ich habe für einen Tag um diesen Fehler zu kämpfen, und fanden die folgende Behelfslösung, die die stellte sich heraus, gelinde schmutzigen Trick:

Statt der Rückkehr kann man die Standard-Windows-API-Funktionsaufruf verwenden Exitthread() um den Thread zu beenden. Diese Methode kann natürlich den internen Zustand des std :: thread-Objekts und der zugehörigen Bibliothek durcheinander bringen, aber da das Programm sowieso beendet wird, nun, sei es so.

#include <windows.h> 

template<typename T> 
class ThreadTest { 
public: 
    ThreadTest() : thread_([] { SleepFor(10); ExitThread(NULL); }) {} 
    ~ThreadTest() { 
    std::cout << "About to join\t" << id() << '\n'; 
    thread_.join(); 
    std::cout << "Joined\t\t" << id() << '\n'; 
    } 
private: 
    std::string id() const { return typeid(decltype(thread_)).name(); } 
    T thread_; 
}; 

Der Join() - Aufruf funktioniert offenbar ordnungsgemäß. Ich entschied mich jedoch für eine sicherere Methode in unserer Lösung. Man kann den Thread HANDLE über std :: thread :: native_handle() bekommen. Mit diesem Griff können wir die Windows-API-Aufruf direkt den Faden zu verbinden:

WaitForSingleObject(thread_.native_handle(), INFINITE); 
CloseHandle(thread_.native_handle()); 

Danach wird das std :: Thread-Objekt darf nicht zerstört werden, da der destructor dem Faden ein zweites Mal zu verbinden versuchen würde. Wir lassen also das std :: thread-Objekt beim Programm-Exit hängen.

6

Ich weiß, dies ist eine alte Frage in Bezug auf VS2012, aber der Fehler ist immer noch in VS2013. Für diejenigen, die auf VS2013 stecken, vielleicht aufgrund der Weigerung von Microsoft, Upgrade-Preise für VS2015 bereitzustellen, biete ich die folgende Analyse und Workaround an.

Das Problem ist, dass der Mutex (at_thread_exit_mutex), der von _Cnd_do_broadcast_at_thread_exit() verwendet wird, entweder noch nicht initialisiert ist oder bereits zerstört wurde, abhängig von den genauen Umständen. Im ersten Fall versucht _Cnd_do_broadcast_at_thread_exit(), den Mutex beim Herunterfahren zu initialisieren, was zu einem Deadlock führt. Im letzteren Fall, in dem der Mutex bereits über den Atexit-Stack zerstört wurde, stürzt das Programm auf dem Weg nach draußen ab.

Die Lösung, die ich gefunden habe, ist, _Cnd_do_broadcast_at_thread_exit() (die glücklicherweise öffentlich deklariert wird) früh während des Programmstarts explizit aufzurufen. Dies hat zur Folge, dass der Mutex erzeugt wird, bevor irgendjemand versucht, darauf zuzugreifen, und dass der Mutex bis zum letzten möglichen Moment bestehen bleibt.

Also, um das Problem zu beheben, fügen Sie den folgenden Code am unteren Rand eines Quellmoduls, zum Beispiel irgendwo unter main().

#pragma warning(disable:4073) // initializers put in library initialization area 
#pragma init_seg(lib) 

#if _MSC_VER < 1900 
struct VS2013_threading_fix 
{ 
    VS2013_threading_fix() 
    { 
     _Cnd_do_broadcast_at_thread_exit(); 
    } 
} threading_fix; 
#endif 
+1

Diese Problemumgehung funktioniert nicht für Visual Studio 2013 Ultimate. – leetNightshade

+0

Funktioniert für mich nicht für ein Singleton aktives Objekt in einer DLL, MSVC++ 2013 Update 5. –

Verwandte Themen