2013-06-13 16 views
17

Mam zaimplementowałem singleton (wersja statyczna) w C++. Znam wszystkie kontrowersje na temat tego wzorca i potencjalnych problemów związanych z bezpieczeństwem wątków, ale jestem ciekawy, dlaczego ta dokładna implementacja nie zatrzyma się. Program nigdy się nie kończy, pozostaje na końcu w stanie zakleszczenia.Dlaczego ten statyczny singleton C++ nigdy się nie zatrzymuje?

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
  • główny wątek utknął w łączeniu
  • ma coś wspólnego ze statycznym, jeśli sprawię, że konstruktor będzie publiczny i utworzy instancję Singleton w main(), to poprawnie się zakończy.

Korzystanie z programu Visual Studio 2012. Dziękujemy za porady.

+5

Mieszanie globalnego zamykania i uruchamiania wątków jest trudne w najlepszym wypadku . W tej chwili nie mogę znaleźć żadnego standardowego odniesienia, ale podejrzewam, że to, co próbujesz zrobić (czy wątek przeszedł przez koniec) jest tak naprawdę niezdefiniowanym zachowaniem. –

+0

Prawdopodobnie dlatego, że instancja statyczna przeżyje 'main()'. – juanchopanza

+0

czy zachowuje się tak samo, jeśli konstruktor jest publiczny i tworzy go jako lokalną zmienną główną? – kassak

Odpowiedz

4

Dziękuję wszystkim za wskazówki. Wygląda na to, że implementacja tego wzorca powoduje zakleszczenie w VC++.

Po wykonaniu dalszych badań znalazłem tę implementację w oparciu o mechanikę 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 Microsoft zdaje sobie sprawę z tego problemu.Na forach VC++ użytkownik o nazwie "dlafleur" poinformował tego posta: https://connect.microsoft.com/VisualStudio/feedback/details/747145

7

Zidentyfikowaliśmy to na void __cdecl _lock(int locknum) wewnątrz mlock.c. Po zakończeniu main() główny wątek trafia tam i wchodzi do sekcji krytycznej EnterCriticalSection(_locktable[locknum].lock);. Następnie Singleton destructor zostaje wywołany, a drugi wątek próbuje wejść do tej samej krytycznej sekcji, ale nie może, i tak zaczyna się czekać, aż główny wątek opuści sekcję krytyczną. Główny wątek czeka z kolei na drugi wątek. Więc myślę, że to błąd.

22

W głównym wątku, po zakończeniu kończenia main(), CRT uzyskuje blokadę wyjścia i wywołuje statyczny destruktor instancji, który czeka na zakończenie wątku podstawowego.

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

To prosty zakleszczenie, które jest spowodowane przez implementację CRT. Najważniejsze jest to, że nie można oczekiwać zakończenia wątku w statycznym destruktorze instancji w systemie Windows.

4

See [basic.start.term] w Standard:

Jeśli jest zastosowanie standardowej biblioteki obiektów lub funkcji nie dozwolone w ramach procedur obsługi sygnałów (18.10), które nie zdarzają się przed (1.10) zakończenie niszczenia obiektów ze statycznym przechowywaniem czas trwania i wykonanie std :: atexit zarejestrowanych funkcji (18,5), program ma niezdefiniowane zachowanie. [Uwaga: W przypadku użycia obiektu ze statycznym czasem przechowywania, który nie nastąpi przed zniszczeniem obiektu, , program ma niezdefiniowane zachowanie. Przerywanie każdego wątku przed wywołaniem std :: exit lub wyjściem z main jest wystarczające, , ale nie jest to konieczne, aby spełnić te wymagania. Wymagania te pozwalają na zarządzenie wątkami jako obiektami do przechowywania statycznego. -end uwaga]