Почему этот статический сингл С++ никогда не останавливается?
Я реализовал singleton (статическая версия) в С++. Я знаю все разногласия по поводу этой модели и потенциальной проблемы безопасности потоков, но мне любопытно, почему эта точная реализация не остановится. Программа никогда не завершается, она остается в состоянии взаимоблокировки в конце.
singleton.h:
#pragma once
#include <thread>
#include <atomic>
class Singleton
{
public:
static Singleton& getInstance();
private:
std::thread mThread;
std::atomic_bool mRun;
Singleton();
~Singleton();
void threadFoo();
};
singleton.cpp
#include "singleton.h"
Singleton& Singleton::getInstance()
{
static Singleton instance;
return instance;
}
Singleton::Singleton()
{
mRun.store(true);
mThread = std::thread(&Singleton::threadFoo, this);
}
Singleton::~Singleton()
{
mRun.store(false);
if(mThread.joinable())
mThread.join();
}
void Singleton::threadFoo()
{
while(mRun)
{
}
}
main.cpp
#include "singleton.h"
int main()
{
Singleton::getInstance();
return 0;
}
Что я уже знаю:
- поток завершается
- основной поток застрял в соединении
- он имеет какое-то отношение к статическому, если я сделаю конструктор общедоступным и создаю экземпляр Singleton в main(), он будет корректно завершен.
Использование Visual Studio 2012. Спасибо за ваш совет.
Ответы
Ответ 1
Хорошо, спасибо всем за ваши намеки. По-видимому, эта реализация шаблона приводит к тупиковой ситуации на VС++.
После дальнейших исследований я нашел эту реализацию на основе механики С++ 11, которая работает в VС++.
singleton.h
#pragma once
#include <thread>
#include <atomic>
#include <memory>
#include <mutex>
class Singleton
{
public:
static Singleton& getInstance();
virtual ~Singleton();
private:
static std::unique_ptr<Singleton> mInstance;
static std::once_flag mOnceFlag;
std::thread mThread;
std::atomic_bool mRun;
Singleton();
void threadFoo();
};
singleton.cpp
#include "singleton.h"
std::unique_ptr<Singleton> Singleton::mInstance = nullptr;
std::once_flag Singleton::mOnceFlag;
Singleton& Singleton::getInstance()
{
std::call_once(mOnceFlag, [] { mInstance.reset(new Singleton); });
return *mInstance.get();
}
Singleton::Singleton()
{
mRun.store(true);
mThread = std::thread(&Singleton::threadFoo, this);
}
Singleton::~Singleton()
{
mRun.store(false);
if(mThread.joinable())
mThread.join();
}
void Singleton::threadFoo()
{
while(mRun.load())
{
}
}
UPDATE
Похоже, Microsoft знает об этой проблеме. На форумах VС++ пользователь с именем "dlafleur" сообщил об этом сообщении:
https://connect.microsoft.com/VisualStudio/feedback/details/747145
Ответ 2
В основном потоке, после завершения main()
, CRT получает блокировку выхода и вызывает ваш статический деструктор экземпляра, который ждет выхода фонового потока.
В фоновом потоке после завершения функции потока CRT пытается получить блокировку выхода, чтобы выполнить некоторую работу по завершению потока. Это блокируется навсегда, потому что блокировка выхода удерживается основным потоком, который ждет выхода этого потока.
Это простой тупик, вызванный реализацией CRT. Суть в том, что вы не можете ждать завершения потока в статическом деструкторе экземпляра в Windows.
Ответ 3
Я прорисовал его до void __cdecl _lock(int locknum)
внутри mlock.c
. Когда main()
заканчивается, основной поток идет туда и входит в критический раздел EnterCriticalSection( _locktable[locknum].lock );
. Затем вызывается Singleton destructor, а другой поток пытается войти в один и тот же критический раздел, но не может, и поэтому он начинает ждать, пока основной поток покинет критический раздел. Основной поток, в свою очередь, ждет другой поток. Поэтому я предполагаю, что это ошибка.
Ответ 4
См. [basic.start.term] в стандарте:
Если используется стандартный объект или функция библиотеки, разрешено в обработчиках сигналов (18.10), которое не происходит раньше (1.10) завершение уничтожения объектов со статическим хранением длительность и исполнение зарегистрированных std:: atexit функций (18.5), программа имеет undefined поведение. [Примечание: если используется объект с продолжительностью статического хранения, которая не возникает перед объектами разрушение, программа имеет undefined поведение. Прекращение каждого нить до вызова std:: exit или выхода из main достаточно, но не обязательно, чтобы удовлетворить эти требования. Эти требования разрешать менеджерам потоков как объекты с продолжительностью статического хранения. -end note]
Ответ 5
Эта ошибка тупика такая же, как в
std:: thread:: join() зависает, если вызвано после выхода main() при использовании VS2012 RC
и он не исправлен в Visual Studio 2013.
Ответ 6
Кажется, это исправлено в Visual Studio 2015 и выше, по крайней мере, для этого конкретного примера.