2013-05-15 30 views
5

スレッドセーフと例外セーフである必要があるコードがあります。以下のコードは私の問題の非常に単純化されたバージョンです:C++のデストラクタでミューテックスをロックする11

#include <mutex> 
#include <thread> 

std::mutex mutex; 
int n=0; 

class Counter{ 
public: 
    Counter(){ 
     std::lock_guard<std::mutex>guard(mutex); 
     n++;} 
    ~Counter(){ 
     std::lock_guard<std::mutex>guard(mutex);//How can I protect here the underlying code to mutex.lock() ? 
     n--;} 
}; 

void doSomething(){ 
    Counter counter; 
    //Here I could do something meaningful 
} 

int numberOfThreadInDoSomething(){ 
    std::lock_guard<std::mutex>guard(mutex); 
    return n;} 

私はオブジェクトのデストラクタをロックする必要があるミューテックスを持っています。問題は、私のデストラクタが例外をスローするべきではないということです。

どうすればよいですか?

0)私はスピンロック

と私のミューテックスを置き換えることができます)(もちろん、それはここにトリックを行うだろうが、それは私の質問のポイントではありません)アトミック変数で

1をnを置き換えることはできません

2)例外なくロックを取得するまで無限ループにロックをキャッチしようとする可能性があります

これらの解決策はどれも魅力的ではありません。同じ問題がありましたか?どのように解決しましたか?

+3

「オブジェクトのデストラクタにロックする必要のあるミューテックスがある」 - 悪い考えのように思える。解決策を私たちに提供し、問題を解決する代わりに、あなたが解決しようとしている問題を教えてください。そうすれば、より良い解決策を提供することができます。 –

+1

@RobertHarvey私が実際にやりたいことは、データベースに保存された後で変更を共有キャッシュに挿入することです。 – Arnaud

答えて

8

アダムH.ピーターソンによって示唆されるように、私は最終的にノースローミューテックスを書くことにしました:

class NoThrowMutex{ 
private: 
    std::mutex mutex; 
    std::atomic_flag flag; 
    bool both; 
public: 
    NoThrowMutex(); 
    ~NoThrowMutex(); 
    void lock(); 
    void unlock(); 
}; 

NoThrowMutex::NoThrowMutex():mutex(),flag(),both(false){ 
    flag.clear(std::memory_order_release);} 

NoThrowMutex::~NoThrowMutex(){} 

void NoThrowMutex::lock(){ 
    try{ 
     mutex.lock(); 
     while(flag.test_and_set(std::memory_order_acquire)); 
     both=true;} 
    catch(...){ 
     while(flag.test_and_set(std::memory_order_acquire)); 
     both=false;}} 

void NoThrowMutex::unlock(){ 
    if(both){mutex.unlock();} 
    flag.clear(std::memory_order_release);} 

アイデアは、代わりに一つだけの2つのミューテックスを持つことです。実際のミューテックスは、std::atomic_flagで実装されたスピンミューテックスです。このスピンミューテックスはstd::mutexによって保護されています。

通常の状況では、標準ミューテックスが取得され、フラグは1アトミック操作のコストで設定されます。標準ミューテックスをすぐにロックすることができない場合、スレッドはスリープ状態になります。

何らかの理由で標準ミューテックスがスローすると、ミューテックスはスピンモードになります。例外が発生したスレッドは、フラグを設定できるまでループします。他のスレッドは、このスレッドが標準ミューテックスを完全にバイパスしていることを認識していないので、それも回転する可能性があります。

この最悪の場合のシナリオでは、このロック機構はスピンロックに劣化します。ほとんどの場合、通常のミューテックスのように反応します。

3

これは悪い状況です。デストラクタは失敗する可能性のある何かをしています。このカウンタの更新に失敗した場合、アプリケーションが回復不能に破損する可能性があります。単にデストラクタにスローさせることができます。これにより、terminateへの呼び出しでアプリケーションがクラッシュしますが、アプリケーションが破損している場合は、プロセスを強制終了し、より高いレベルの回復スキーム(デーモンのウォッチドッグや別のユーティリティの実行の再試行など) 。カウンターを減らすことができない場合は、ブロックtry{}catch()を使用して例外を吸収し、回復する必要があります(または、最終的に回復するために情報を保存する可能性があります)。回復可能ではないが致命的でない場合は、例外をキャッチして吸収し、その失敗を記録することができます(もちろん、例外的な方法でログインしてください)。

デストラクタが失敗できないようなコードを再構成できれば理想的です。しかし、あなたのコードが正しくなければ、ロックを取得している間の失敗はおそらく稀であるため、リソースが制限されている場合を除いて、失敗した場合に吸収または中断することは非常にうまくいくかもしれません。いくつかのmutexでは、lock()はおそらくノースロー操作(atomic_flagを使用するスピンロックなど)であり、このようなミューテックスを使用できる場合、lock_guardは決して投げないことが予想されます。その状況で唯一の心配はデッドロックです。

関連する問題