Dlaczego ten C++ statyczny singleton nigdy się nie kończy?

Zaimplementowałem singleton (wersja statyczna) w C++. Znam wszystkie kontrowersje dotyczące tego wzoru i potencjalnych problemów z bezpieczeństwem wątku, ale jestem ciekaw, dlaczego ta dokładna implementacja nie zatrzyma się. Program nigdy się nie kończy, na końcu pozostaje w stanie impasu.

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

Co już wiem:

  • wątek kończy się
  • główny wątek tkwi w join
  • ma to coś wspólnego ze statyką, jeśli upublicznię konstruktor i stworzę instancję Singletona w main() To zakończy się poprawnie.

Za Pomocą Visual Studio 2012. Dzięki za radę.

Author: Vincenzo Pii, 2013-06-13

5 answers

W głównym wątku, po zakończeniu main(), CRT przejmuje blokadę wyjścia i wywołuje destruktor wystąpienia statycznego, który czeka na zakończenie wątku w tle.

W wątku tła, po zakończeniu funkcji wątku, CRT próbuje uzyskać blokadę wyjścia, aby wykonać pewne czynności związane z zakończeniem wątku. Blokuje to na zawsze, ponieważ blokada wyjścia jest utrzymywana przez główny wątek, który czeka na wyjście tego wątku.

To prosty impas, który jest spowodowany przez wdrożenie CRT. Najważniejsze jest to, że nie możesz czekać na zakończenie wątku w statycznym destruktorze instancji w systemie Windows.

 22
Author: BitCortex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-13 18:48:04

Wyśledziłem to do void __cdecl _lock(int locknum) wewnątrz mlock.c. Po zakończeniu main() główny wątek przechodzi tam I wchodzi do sekcji krytycznej EnterCriticalSection( _locktable[locknum].lock );. Następnie wywoływany jest Destruktor Singleton, a drugi wątek próbuje wejść do tej samej sekcji krytycznej, ale nie może, więc zaczyna czekać, aż główny wątek opuści sekcję krytyczną. Główny wątek z kolei czeka na drugi wątek. Więc to chyba pluskwa.

 7
Author: catscradle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-13 18:47:09

Zobacz [podstawowe.zaczynaj.term] w standardzie:

Jeśli istnieje użycie standardowego obiektu biblioteki lub funkcji nie dozwolone w modułach obsługi sygnałów (18.10), które nie występują przed (1.10) zakończenie niszczenia obiektów statycznie przechowywanych czas trwania i wykonanie std::atexit registered functions (18.5), the program Ma nieokreślone zachowanie. [Uwaga: Jeśli istnieje użycie obiektu z statycznym czasem przechowywania, który nie następuje przed zniszczenie, program Ma nieokreślone zachowanie. Zakończenie każdego wątek przed wywołaniem std:: exit lub exit z main jest wystarczający, ale nie jest to konieczne, aby spełnić te wymagania. Wymagania te Zezwalaj menedżerom wątków jako obiektom statycznym-storage-duration. - Uwaga końcowa]

 4
Author: Nikolay Khil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-13 20:51:11

Ok dziękuję wszystkim za podpowiedzi. Najwyraźniej ta implementacja wzorca powoduje impas w VC++.

Po przeprowadzeniu dalszych badań znalazłem tę implementację opartą na mechanice C++11, która działa w VC++.

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

Wygląda na to, że Microsoft jest świadomy tego problemu. Na forach VC++ użytkownik "dlafleur" zgłosił to post: https://connect.microsoft.com/VisualStudio/feedback/details/747145
 4
Author: Xcessity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-14 08:35:39

Ten błąd jest taki sam jak w

Std::thread:: join() zawiesza się, jeśli zostanie wywołane po wyjściu main() podczas używania VS2012 RC

I nie jest to naprawione w Visual Studio 2013.

 1
Author: Timo Bingmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:45:43