2016-09-02 32 views
0

私は最近std :: mutexを使用していて、メンバとしてstd :: mutexを持つオブジェクトを削除する際のパターン/デザインガイドを探しています。この問題は、ミューテックス(モニタ関数、クリティカルセクションなど)を使用する公開関数としてのオブジェクトが存在し、このオブジェクトが削除されたときにスレッドがミューテックスを待機している可能性があります。 std :: mutexは未定義の動作を他のスレッドによってロックされている間に削除してしまい、問題が発生します。メンバstd :: mutexを安全に削除する方法?

この場合、一般に受け入れられるパターンが何であるか知りたいです。あるいは、これが悪いコーディングスタイルとみなされた場合、この設計を避ける方法、すなわち、mutexメソッドがまだ待っているオブジェクトを削除しないでください。

例:

//a public method that uses mutex. 
IAsyncAction^ XInputBase::flushTask() 
{ 
    return create_async([this](){ 
     _monitorMutex.lock(); 
     if (_readyToFlush && !_noMoreFlush) { 
      //should flush only once, block additional flush signals. 
      _noMoreFlush = true; 
      _monitorMutex.unlock(); 
      //actually flush 
      concurrency::task<void> UITask = concurrency::create_task(Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, 
       ref new Windows::UI::Core::DispatchedHandler([=]() 
      { 
       onFlush(); 
      }))); 
     } 
     else { 
      _needToFlush = true; 
      _monitorMutex.unlock(); 
     }   
    }); 
} 

しようとしたソリューション: デストラクタは、すべての待機中のスレッドが処理された後、完全にロックを解除するためにミューテックスを待っています。私はmutexの外側にフラグを設定して実装しました。そのため、メソッド内のすべてのスレッドは、ミューテックスを終了するか待機します。その後、私はstd :: mutexをデストラクタで最後にロックしました。 std :: mutexが公平であるとすれば、他の待機中のスレッドが処理された後でデストラクターを最後にアンロックし、オブジェクトを破棄する必要があります。

std :: mutexはほとんどのプラットフォームで公平性を保証していないため、これは機能しません。

解決策のソート: オブジェクトをrefクラスまたは他の参照カウントオブジェクト(スマートポインタ)として実装します。その後、オブジェクトへの強い参照を保持するラムダとして並行メソッドを実装します。このオブジェクトを保持する他のすべてのオブジェクトが削除され、mutexを持つすべてのlamdasが処理されると、このオブジェクトは自動的に削除されます。

私はこのメソッドが他のコードにいくつかの制限を課しているので、このメソッドが嫌いです。 WinRTの場合、このメソッドはどのスレッドがこのオブジェクトを削除するかを制御しません。その結果、UIスレッドエラーが発生する可能性があります。

+0

これはmutexに固有のものではありません。オブジェクトのメンバがまだ使用されているときは、オブジェクトを削除できません。 – MSalters

+0

@MSalters並行処理が難しいです。単一スレッドのコードでは、デストラクタまたはメンバ関数を同時に実行することはできません。また、オブジェクトが不要になったときにオブジェクトが削除されるため、カウントされたオブジェクトやRAIIを使用することで、常に安全な削除を保証できます。循環参照などは例外です。 – legokangpalla

答えて

1

オブジェクト内のデータでオブジェクトの有効期間を保護することはできません。

オブジェクトを外部ミューテックスで保護することができます。

だから、これで始まる:

locked<int> my_int = 7; 

my_int.read()->*[](int x){ std::cout << x << '\n'; }; 

次に、原料それにstd::unique_ptr<YourClass>

template<class T> 
struct locked_view { 
    template<class F> 
    auto operator->*(F&& f) const 
    -> std::result_of_t< F(T const&) > 
    { 
    auto l = lock(); 
    return std::forward<F>(f)(*t); 
    } 
    locked_view(locked_view const&) = default; 
    locked_view& operator=(locked_view const&) = default; 
    locked_view()=default; 
    explicit operator bool() const { return m&&t; } 

    locked_view(std::mutex* min, T* tin):m(min), t(tin) {} 

private: 
    std::unique_lock<std::mutex> lock() const { 
    return std::unique_lock<std::mutex>(*m); 
    } 
    std::mutex* m; 
    T* t; 
}; 

template<class T> 
struct locked { 
    locked()=default; 
    locked(locked&& o): 
    t(o.move_from()) 
    {} 
    locked(locked const& o): 
    t(o.copy_from()) 
    {} 
    locked& operator=(locked&& o) { 
    auto tin = o.move_from(); 
    assign_to(std::move(tin)); 
    return *this; 
    } 
    locked& operator=(locked const& o) { 
    auto tin = o.copy_from(); 
    assign_to(std::move(tin)); 
    return *this; 
    } 

    template<class U, 
    std::enable_if_t<!std::is_same<std::decay_t<U>, locked>{}, int> =0 
    > 
    locked(U&& u): 
    t(std::forward<U>(u)) 
    {} 

    // stars of show: 
    locked_view<T const> read() const 
    { 
    return {&m, std::addressof(t)}; 
    } 
    locked_view<T> write() 
    { 
    return {&m, std::addressof(t)}; 
    } 

    T move_from() { 
    return write()->*[](T& tin){return std::move(tin);}; 
    } 
    T copy_from() const { 
    return read()->*[](T const& tin){return tin;}; 
    } 
    template<class U> 
    void assign_to(U&& u) { 
    write()->*[&](T& t){ t = std::forward<U>(u); }; 
    } 
private: 
    mutable std::mutex m; 
    T t; 
}; 

使用は次のようになります。これにより、人々はそれを削除することができます。

最後に、std::shared_ptr<>を共有します。

ので

template<class T> 
using shared_locked = std::shared_ptr< locked<T> >; 
template<class T> 
using shared_locked_ptr = shared_locked< std::unique_ptr<T> >; 

はあなたのタイプです。使用するには

、彼らはこれを行う:

あなたがロックされたブロック内の寿命を確認するために取得
shared_locked_ptr<widget> w; 
w->read()->*[&](auto& ptr) { 
    if (ptr) ptr->do_something(); 
}; 

。ロックはウィジェットに、ウィジェットではない周りのポインタの周りにある

w->write()->*[&](auto& ptr) { 
    ptr = {}; 
}; 

:スレッドセーフにオブジェクトを削除

。ウィジェットへのポインタへのポインタは共有されています。

コードはテストされていませんが、これまで同様の設計が行われています。

関連する問題