Std:: thread:: join() зависает, если вызвано после выхода main() при использовании VS2012 RC
Следующий пример выполняется успешно (т.е. не зависает), если скомпилирован с использованием Clang 3.2 или GCC 4.7 на Ubuntu 12.04, но зависает, если я компилирую с помощью VS11 Beta или 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);
}
Проблема заключается в том, что std::thread::join()
никогда не возвращается, если он вызывается после выхода main
. Он заблокирован в WaitForSingleObject
в _Thrd_join
, определенном в cthread.c.
Раскомментирование SleepFor(100);
в конце main
позволяет программе правильно выйти, как и сделать std_test
нестатический. Использование boost::thread
также позволяет избежать проблемы.
Итак, я хотел бы знать, если я вызываю поведение undefined здесь (кажется маловероятным для меня), или если я должен подавать ошибку против VS2012?
Ответы
Ответ 1
Трассировка по образцу кода Фрейзера в его ошибке подключения (https://connect.microsoft.com/VisualStudio/feedback/details/747145) с RT2012 RTM, похоже, показывает довольно простой случай взаимоблокировки. Вероятно, это не относится к std::thread
- вероятно, _beginthreadex
испытывает ту же участь.
В отладчике я вижу следующее:
В основном потоке функция main()
завершена, код очистки процесса приобрел критический раздел под названием _EXIT_LOCK1
, называемый деструктором ThreadTest
, и ждет (неопределенно) во втором потоке, чтобы выйти (по вызову join()
).
Вторая анонимная функция потока завершена и находится в коде очистки потока, ожидающем получения критического раздела _EXIT_LOCK1
. К сожалению, из-за времени вещей (при котором время анонимной функции второго потока больше, чем функция main()
), основной поток уже владеет этой критической секцией.
ТУПИК.
Все, что продлевает срок службы main()
, так что второй поток может получить _EXIT_LOCK1
до того, как основной поток избежит ситуации взаимоблокировки. Поэтому недопустимый сон в main()
приводит к чистому отключению.
Альтернативно, если вы удаляете статическое ключевое слово из локальной переменной ThreadTest
, вызов деструктора перемещается до конца функции main()
(а не в код очистки процесса), который затем блокируется до тех пор, пока второй поток не будет выйти - избежать ситуации взаимоблокировки.
Или вы можете добавить функцию к ThreadTest
, которая вызывает join()
и вызывать эту функцию в конце main()
- снова, избегая ситуации взаимоблокировки.
Ответ 2
Я понимаю, что это старый вопрос относительно VS2012, но ошибка все еще присутствует в VS2013. Для тех, кто застрял на VS2013, возможно, из-за отказа Microsoft предоставить цену обновления для VS2015, я предлагаю следующий анализ и обходной путь.
Проблема в том, что мьютекс (at_thread_exit_mutex
), используемый _Cnd_do_broadcast_at_thread_exit()
, либо еще не инициализирован, либо уже уничтожен, в зависимости от конкретных обстоятельств. В первом случае _Cnd_do_broadcast_at_thread_exit()
пытается инициализировать мьютекс во время выключения, вызывая тупик. В последнем случае, когда мьютекс уже был уничтожен через стек atexit, программа выйдет из строя на выходе.
Решение, которое я нашел, заключается в явном вызове _Cnd_do_broadcast_at_thread_exit()
(который, к счастью, объявлен публично), во время запуска программы. Это приводит к созданию мьютекса, прежде чем кто-либо еще попытается получить к нему доступ, а также гарантирует, что мьютекс продолжает существовать до последнего момента.
Итак, чтобы устранить проблему, вставьте следующий код внизу исходного модуля, например, где-то ниже 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
Ответ 3
Я считаю, что ваши потоки уже были прекращены и их ресурсы освобождены после прекращения вашей основной функции и до статического разрушения. Это поведение времени выполнения VC, относящегося, по меньшей мере, к VC6.
Выполняются ли дочерние потоки, когда родительский поток завершается
увеличить поток и очистить процесс в окнах
Ответ 4
Я боролся с этой ошибкой в течение дня и нашел следующую работу, которая оказалась наименее грязной трюкой:
Вместо того, чтобы возвращаться, можно использовать стандартный вызов функции Windows API ExitThread() для завершения потока. Разумеется, этот метод может испортить внутреннее состояние объекта std:: thread и связанной с ним библиотеки, но поскольку программа все равно прекратится, ну, пусть это будет.
#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_;
};
Похоже, что вызов join() работает правильно. Тем не менее, я решил использовать более безопасный метод в нашем решении. Можно получить поток HANDLE через std:: thread:: native_handle(). С помощью этого дескриптора мы можем напрямую вызвать API Windows для присоединения к потоку:
WaitForSingleObject(thread_.native_handle(), INFINITE);
CloseHandle(thread_.native_handle());
Впоследствии объект std:: thread не должен быть уничтожен, так как деструктор попытается присоединиться к потоку во второй раз. Поэтому мы просто оставляем объект std:: thread висящим при выходе программы.