Вопрос по c++, c++11, visual-c++-2012, stdthread, visual-c++ – std :: thread :: join () зависает, если вызывается после выхода из main () при использовании VS2012 RC

23

Следующий пример выполняется успешно (т.е. не зависает), если скомпилирован с использованием 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 также избегает проблемы.

Итак, я хотел бы знать, вызываю ли я здесь неопределенное поведение (кажется мне маловероятным), или мне следует сообщать об ошибке в VS2012?

Пожалуйста, отправьте сообщение об ошибке наMicrosoft Connect (Я не знаю, является ли этоactually Жук; Я не настолько знаком сstd::thread Спецификация. Но все же стоило бы отчитываться.) James McNellis
@Plexicothread_.joinable() возвращаетсяtrue. Fraser
Проблемы с бета-версией и релиз-кандидатом? Jaywalker
Не могли бы вы попробовать проверить, еслиstd::thread::joinable() все еще возвращает истину перед зависанием? Plexico
@ Jaywalker Ну, я думаю, это то, что я спрашиваю. Разумно ли ожидатьjoin() не висеть, если он вызван послеmain вышел. Если это так, то это ошибка, которую я хотел бы получить в MS Connect раньше, чем позже. Fraser

Ваш Ответ

4   ответа
6

что это старый вопрос относительно 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
Этот обходной путь не работает для меня в Visual Studio 2013 Ultimate.
У меня не работает ни один активный объект в DLL, MSVC ++ 2013, обновление 5.
-1

который оказался наименее подвохом:

Вместо возврата можно использовать стандартный вызов функции 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 (). С помощью этого дескриптора мы можем вызвать Windows API напрямую, чтобы присоединиться к потоку:

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

После этого объект std :: thread не должен быть уничтожен, поскольку деструктор попытается присоединиться к потоку во второй раз. Поэтому мы просто оставляем объект std :: thread висящим при выходе из программы.

25

https://connect.microsoft.com/VisualStudio/feedback/details/747145) с VS2012 RTM, кажется, показывает довольно простой случай взаимоблокировки. Это, вероятно, не является специфическим дляstd::thread - скорее всего_beginthreadex постигла та же участь.

В отладчике я вижу следующее:

В основной темеmain() функция завершена, код очистки процесса получил критическую секцию под названием_EXIT_LOCK1называется деструкторомThreadTestи ожидает (неопределенно) второго потока для выхода (через вызовjoin()).

Анонимная функция второго потока завершена и находится в коде очистки потока, ожидающем получения_EXIT_LOCK1 критический раздел. К сожалению, из-за временной привязки вещей (вследствие чего время жизни анонимной функции второго потока превышает время жизниmain() функция) основной поток уже владеет этим критическим разделом.

DEADLOCK.

Все, что продлевает срок службыmain() так, что второй поток может приобрести_EXIT_LOCK1 прежде чем основной поток избегает тупиковой ситуации. Вот почему комментирование сна вmain() приводит к чистому отключению.

В качестве альтернативы, если вы удалите статическое ключевое слово изThreadTest локальная переменная, вызов деструктора перемещается до концаmain() функция (вместо кода очистки процесса), которая затем блокируется до выхода из второго потока, что позволяет избежать ситуации взаимоблокировки.

Или вы можете добавить функциюThreadTest что вызываетjoin() и вызвать эту функцию в концеmain() - снова избегая тупиковой ситуации.

@Frazer - Готово! Рад, что помог!
Хороший анализ. ~
Невероятно, что эта проблема все еще существует и затрагивает потоки C ++ 11. Я видел подобное поведение с портом pthreads в Windows 10 лет назад ...
@CWoods - можете ли вы вставить в свой ответ ссылку на мой отчет об ошибке MS Connect? Я хочу пометить вашу как правильную и удалить свою. Fraser
Да, ошибка все еще существует в MSVC ++ 2013 для_beginthreadex() тоже: я только что рефакторинг с C ++ 11thread в MSVC ++_beginthreadex(), ни за что. MSDN также говорит, чтоCreateThread не может быть использован для потоков, которые используют CRT (и мне нужноFILE* операции):msdn.microsoft.com/en-us/library/windows/desktop/… , Также нет возможности рефакторинга в нестатическую переменную, так как потоки выполняются в одиночном журнале. Таким образом, MSVC ++ 2013 не работает с многопоточностью, а CUDA Toolkit еще не поддерживает MSVC ++ 2015. % (
1

что ваши потоки уже прерваны, а их ресурсы освобождены после завершения вашей основной функции и до статического уничтожения. Это поведение сред выполнения VC, восходящих по крайней мере к VC6.

Выход дочерних потоков завершается, когда родительский поток завершается

увеличить поток и очистить процесс на окнах

Я могу приостановить выполнение, пока программа зависает, и видеть стеки для основного потока, пытающегося завершитьjoin вызов, и дочерний поток в_lock mlock.c, поэтому потоки не завершены. Кроме того, ваш ответ действительно не отвечает на мой вопрос. Это зависание в результате неопределенного поведения или ошибки в VS2012? Fraser
Я использую Windows, но, к сожалению, я не уверен, что ваш ответ правильный. Основным потоком является тот, который выполняетjoinтак что, хотя функцияmain выход завершен, основной поток не завершен. Спасибо, в любом случае. Fraser
Более актуальное содержание:stackoverflow.com/questions/8001989/…
Этот ответ специфичен для окон, но я предположил, что вы используете VS.
Возможно, код, который убивает потоки, находится где-то во время выполнения за пределами основного вызова, но я могу заверить вас, что ваши потоки исчезнут к тому времени, когда вызываются ваши деструкторы.

Похожие вопросы