x64 visual microsoft c++ visual-c++ c++11 stdthread visual-c++-2012

c++ - visual - std:: thread:: join() se cuelga si se llama después de que main() sale al usar VS2012 RC



microsoft visual c++ 2017 redistributable package(x64) (4)

Creo que sus hilos ya se terminaron y sus recursos se liberaron después de la terminación de su función principal y antes de la destrucción estática. Este es el comportamiento de los tiempos de ejecución de VC que se remontan al menos a VC6.

¿Los hilos secundarios se cierran cuando el hilo primario termina?

Impulsar el hilo y el proceso de limpieza en Windows

El siguiente ejemplo se ejecuta correctamente (es decir, no se bloquea) si se compila con Clang 3.2 o GCC 4.7 en Ubuntu 12.04, pero se bloquea si compilo con VS11 Beta o 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); }

El problema parece ser que std::thread::join() nunca regresa si se invoca después de que main haya salido. Se bloquea en WaitForSingleObject en _Thrd_join definido en cthread.c.

Sin comentar SleepFor(100); al final de main permite al programa salir correctamente, al igual que hacer std_test no estático. El uso de boost::thread también evita el problema.

Entonces, me gustaría saber si estoy invocando un comportamiento indefinido aquí (parece poco probable para mí), o si debería estar presentando un error en contra de VS2012.


El rastreo a través del código de muestra de Fraser en su error de conexión ( https://connect.microsoft.com/VisualStudio/feedback/details/747145 ) con VS2012 RTM parece mostrar un caso bastante sencillo de interbloqueo. Es probable que esto no sea específico de std::thread : es probable que _beginthreadex el mismo destino.

Lo que veo en el depurador es lo siguiente:

En el hilo principal, la función main() ha completado, el código de limpieza del proceso ha adquirido una sección crítica llamada _EXIT_LOCK1 , llamada el destructor de ThreadTest , y está esperando (indefinidamente) en el segundo hilo para salir (a través de la llamada para join() ).

La función anónima del segundo subproceso se completó y está en el código de limpieza del subproceso en espera de adquirir la sección crítica _EXIT_LOCK1 . Desafortunadamente, debido a la sincronización de las cosas (donde la vida útil de la función anónima del segundo subproceso supera a la de la función main() ), el subproceso principal ya posee esa sección crítica.

PUNTO MUERTO.

Cualquier cosa que extienda la vida útil de main() modo que el segundo subproceso pueda adquirir _EXIT_LOCK1 antes de que el subproceso principal evite la situación de interbloqueo. Es por eso que al no comentar el sueño en main() produce un cierre limpio.

Alternativamente, si elimina la palabra clave estática de la variable local ThreadTest , la llamada del destructor se mueve hasta el final de la función main() (en lugar de en el código de limpieza del proceso) que luego se bloquea hasta que el segundo hilo haya salido, evitando el interbloqueo situación.

O puede agregar una función a ThreadTest que llame a join() y llamar a esa función al final de main() , evitando nuevamente la situación de interbloqueo.


He estado luchando contra este error por un día, y encontré la siguiente solución, que resultó ser el truco menos sucio:

En lugar de regresar, uno puede usar la función estándar de la API de Windows llamada ExitThread () para terminar el hilo. Por supuesto, este método puede alterar el estado interno del objeto std :: thread y la biblioteca asociada, pero como el programa terminará de todos modos, bueno, que así sea.

#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_; };

La llamada join () aparentemente funciona correctamente. Sin embargo, elegí usar un método más seguro en nuestra solución. Uno puede obtener el hilo HANDLE a través de std :: thread :: native_handle (). Con este identificador podemos llamar directamente a la API de Windows para unirnos al hilo:

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

A partir de entonces, el objeto std :: thread no debe ser destruido, ya que el destructor intentaría unirse al hilo una segunda vez. Así que dejamos el objeto std :: thread colgando en la salida del programa.


Me doy cuenta de que esta es una pregunta antigua relacionada con VS2012, pero el error aún está presente en VS2013. Para aquellos que están estancados en VS2013, tal vez debido a la negativa de Microsoft a proporcionar precios de actualización para VS2015, ofrezco el siguiente análisis y solución.

El problema es que el mutex ( at_thread_exit_mutex ) utilizado por _Cnd_do_broadcast_at_thread_exit() aún no se ha inicializado o ya se ha destruido, según las circunstancias exactas. En el primer caso, _Cnd_do_broadcast_at_thread_exit() intenta inicializar la _Cnd_do_broadcast_at_thread_exit() durante el cierre, causando un interbloqueo. En este último caso, donde el mutex ya ha sido destruido a través de la pila atexit, el programa se bloqueará al salir.

La solución que encontré es llamar explícitamente a _Cnd_do_broadcast_at_thread_exit() (que afortunadamente se declara públicamente) temprano durante el inicio del programa. Esto tiene el efecto de crear el mutex antes de que alguien más intente acceder a él, así como de garantizar que el mutex continúe existiendo hasta el último momento posible.

Entonces, para solucionar el problema, inserte el siguiente código en la parte inferior de un módulo de origen, por ejemplo, en algún lugar debajo de 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