3

私には厄介な問題が発生しました。簡単に回避できる状況を見つけたようですが、a) プログラミング中に集中力が途切れた場合、または b) 他の誰かが私のインターフェイスの実装を開始し、処理方法がわからない場合、問題が発生する可能性があります。この状況。

これが私の基本的なセットアップです:

いくつかのデータ型への汎用インターフェイスとして使用している抽象クラスがあります。非仮想パブリック インターフェイス パラダイム (Sutter、2001 年) とスコープ ロックを採用して、ある程度のスレッド セーフを実現しました。インターフェイス クラスの例は次のようになります (スコープ付きロックとミューテックスの実装に関する詳細は、関連性がないと思われるため省略しました)。

class Foo
{
public:
    A( )
    {
        ScopedLock lock( mutex );
        aImp( );
    }
    B( )
    {
        ScopedLock lock( mutex );
        bImp( );
    }
protected:
    aImp( ) = 0;
    bImp( ) = 0;
}

aImp と bImp を実装するのはユーザー次第ですが、ここで問題が発生します。aImp が bImp を使用する何らかの操作を実行する場合、これを行うのは非常に簡単です (ある意味ではほとんど論理的です)。

class Bar
{
protected:
    aImp( )
    {
        ...
        B( );
        ...
    }
    bImp( )
    {
        ...
    }
}

デッドロック。もちろん、これに対する簡単な解決策は、パブリック バリアントではなく、保護された仮想関数を常に呼び出すことです (上記のスニペットで B( ) を bImp( ) に置き換えます)。しかし、私がミスを犯したり、さらに悪いことに、他の人が首を吊るすのを許したりした場合、簡単に首を吊るすことはできないようです.

抽象クラスの実装者がコンパイル時にこれらのパブリック関数を呼び出さないようにするか、デッドロックの解決策を回避するのに役立つ方法はありますか?

キックのために、いくつかのミューテックスは、デッドロックの問題を回避する操作を可能にします。例として、Windows 関数の EnterCriticalSection と LeaveCriticalSection を使用してこれを実装すると、問題はありません。しかし、プラットフォーム固有の機能は避けたいと思います。私は現在、スコープ付きロックの実装でboost::mutexとboost::shared_mutexを使用していますが、私が見た限りでは、デッドロックを回避しようとはしていません(私はほとんど好きだと思います)。

4

3 に答える 3

8

プライベート継承を使用すると、問題が解決する可能性があります。

class Foo
{
public:
  void A( )
    {
      ScopedLock lock( mutex );
      aImp( );
    }
  void B( )
    {
      ScopedLock lock( mutex );
      bImp( );
    }

protected:
  virtual void aImp( ) = 0;
  virtual void bImp( ) = 0;
};

class FooMiddle : private Foo
{
public:
  using Foo::aImp;
  using Foo::bImp;
};

class Bar : public FooMiddle
{
  virtual void aImpl ()
  {
    bImp ();
    B ();                   // Compile error - B is private
  }
};

Fooからプライベートに派生し、FooMiddleを使用すると、BarがAまたはBにアクセスできなくなります。ただし、barは引き続きaImpおよびbImpをオーバーライドでき、FooMiddleで宣言を使用すると、これらをBarから呼び出すことができます。

または、問題を解決するのに役立つが解決しないオプションは、Pimplパターンを使用することです。あなたは次のようなものになってしまうでしょう:

class FooImpl
{
public:
  virtual void aImp( ) = 0;
  virtual void bImp( ) = 0;
};

class Foo
{
public:
  void A( )
    {
      ScopedLock lock( mutex );
      m_impl->aImp( );
    }
  void B( )
    {
      ScopedLock lock( mutex );
      m_impl->bImp( );
    }

private:
  FooImpl * m_impl;
}

利点は、FooImplから派生したクラスでは、「Foo」オブジェクトがなくなったため、「A」または「B」を簡単に呼び出すことができないことです。

于 2009-05-07T14:19:40.323 に答える
6

ミューテックスは、再帰的ミューテックスであってはなりません。再帰的ミューテックスでない場合、同じスレッドでミューテックスをロックしようとすると、そのスレッドがブロックされます。そのスレッドはミューテックスをロックしましたが、そのミューテックスでブロックされているため、デッドロックが発生しています。

あなたはおそらく見たいと思うでしょう:

boost::recursive_mutex

http://www.boost.org/doc/libs/1_32_0/doc/html/recursive_mutex.html

クロスプラットフォームで再帰的なミューテックス動作を実装することになっています.Win32 CRITICAL_SECTION(Enter/LeaveCriticalSectionを介して使用される)は再帰的であり、説明した動作を作成します。

于 2009-05-07T14:18:45.863 に答える
1

再帰ロックは問題を解決しますが、必要な場合もありますが、多くの場合、再帰ロックは簡単な方法として使用され、ロックが多すぎると常に感じています。

投稿されたコードはデモンストレーションのために明らかに簡略化されているため、適用されるかどうかはわかりません。

例として、リソースXの使用はスレッドセーフではないとします。あなたはのようなものを持っています。

A() {
   ScopedLock
   use(x)
   aImp()
   use(x)
}

aImp() {
   ScopedLock
   use(x)
}

明らかに、これはデッドロックになります。

ただし、ロックをはるかに狭く使用すると、問題が解消されます。デッドロックの回避として、パフォーマンス上の理由から、可能な限り小さなスコープでロックを使用することは常に良い考えです。

A() {
   {
      ScopedLock
      use(x)
   }
   aImp()
   {
      ScopedLock
      use(x)
   }
}

あなたはその考えを理解します。

それがあなたの問題に当てはまるかどうかわからない詳細を知らなければ、これが常に可能であるとは限らない(またはひどく非効率的なコードにつながる)ことを私は知っています。しかし、とにかく投稿する価値があると思いました。

于 2009-05-07T14:53:49.210 に答える