Подтвердить что ты не робот

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?

4b9b3361

Ответ 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 висящим при выходе программы.