2013-06-13 31 views
17

私はC++でシングルトン(静的バージョン)を実装しています。私はこのパターンと潜在的なスレッド安全性の問題に関するすべての論争を知っていますが、なぜこの正確な実装が止まらないのか不思議です。プログラムは決して終了せず、最後にはデッドロック状態のままです。なぜこのC++スタティックシングルトンは止まらないのですか?

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

  • スレッドが終了する
  • メインスレッドが結合にスタックされている
  • これは、コンストラクタをpublicにしてmain()内のSingletonのインスタンスを作成すると、静的と何か関係があります。

Visual Studio 2012を使用してください。

+5

に固定されていません。私は現時点では標準的な参照を見つけることができませんが、私はあなたがしようとしていること(スレッドがメインの終わりを過ぎていること)が実際には未定義の動作であるという疑いがあります。 –

+0

おそらく、静的インスタンスが 'main()'よりも寿命が長いためです。 – juanchopanza

+0

コンストラクタをpublicにしてローカルメインの変数として作成すると、同じ動作をしますか? – kassak

答えて

4

お願い、ありがとうございます。どうやら、このパターン実装はVC++のデッドロックを引き起こします。

さらに詳しい研究をしたところ、VC++で作業しているC++ 11のメカニックに基づいてこの実装が見つかりました。

#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

singleton.h

Microsoftはこの問題を認識しているように見えます。VC++フォーラムでは、「dlafleur」という名前のユーザーがこのポストを報告: https://connect.microsoft.com/VisualStudio/feedback/details/747145

7

私はこれをvoid __cdecl _lock(int locknum)mlock.cの中にトレースしました。 main()が終了すると、メインスレッドがそこに進み、クリティカルセクションEnterCriticalSection(_locktable[locknum].lock);に入ります。その後、Singletonデストラクタが呼び出され、他のスレッドは同じクリティカルセクションを入力しようとしますが、実行できません。したがって、メインスレッドがクリティカルセクションを離れるのを待って開始します。メインスレッドは、もう一方のスレッドを待ちます。だから私はバグだと思う。

22

メインスレッドでは、main()が終了すると、CRTは終了ロックを取得し、静的インスタンスデストラクタを呼び出し、バックグラウンドスレッドが終了するのを待ちます。

バックグラウンドスレッドでは、スレッド関数が終了した後、CRTは終了ロックを取得してスレッド終了処理を試みます。これは、終了ロックがメインスレッドによって保持されているために永遠にブロックされます。スレッドが終了するのを待機しています。

これは、CRTの実装によって引き起こされる単純なデッドロックです。要点は、Windows上の静的インスタンスデストラクタでスレッド終了を待つことができないということです。

4

標準で[basic.start.term]を参照してください:

を標準ライブラリのオブジェクトや関数の使用がある場合 (1.10)の静的記憶域を持つオブジェクトの破棄が完了し、std :: atexit登録された関数(18.5)を実行して実行する前に発生しない、シグナルハンドラ(18.10)内では許可されていない のプログラムは未定義の動作をします。 [注:オブジェクトの が、オブジェクトの 破壊の前に発生しない静的記憶期間を使用する場合、プログラムには未定義の動作があります。これらの要件を満たすには、 スレッドをすべてstd :: exitまたはmainからの呼び出しの前に終了させることで十分ですが、 でもかまいません。これらの要件 はスレッドマネージャを静的記憶域期間オブジェクトとして許可します。 -end注意]

関連する問題