2011-11-11 9 views
12

たとえば、勘定残高のコレクションを考えてみましょう。そして、あなたはいくつかの異なる口座の残高を確認し、次にいくつかの異なる口座の残高を調整する必要がある複雑な機能を持っています。操作は、コレクションの他のユーザーに関しては不可分である必要があります。この種のアトミック性を提供することを主な仕事とするコレクションクラスがあります。 「正しい」方法は何ですか?どのようにスコープロックを返すことができますか?

私はboost :: mutexメンバーを持つクラスを持っています。問題は、呼び出し側がミューテックスを保持しながらクラスに一連の呼び出しを実行する必要があることです。しかし、私はミューテックスのクラスフリーの統治の外にコードを与えたくありません。私がやりたいのは何

は、この(擬似コード)のようなものです:

class MyClass 
{ 
private: 
    boost::mutex mLock; 
public: 
    boost::scoped_lock& ScopedLock(return ScopedLock(mLock)); 
} 

そのように、発信者はこれを行うことができます:

MyClass f; 
if(foo) 
{ 
boost::scoped_lock lock(f->GetScopedLock()); 
f->LockedFunc1(); 
f->LockedFunc2(); 
} 

アイデアはありLockedFunc1LockedFunc2ことロックが保持された状態で呼び出されます。 lockのデストラクタはf->mLockのロックを解除します。

私は2つの基本的な質問がある:

1)私はこれをどのように行うことができますか?

2)これは合理的ですか?

注:これは、同様の名前の質問return a boost::scoped_lockとはまったく異なります。

+1

これは別の問題を探して私はつまずいた。しかし、私に打撃を与えるのは、MyClassのロックアウト*を渡すことが、本当にあなたがやりたいことの反対です。代わりに、MyClassのメソッド "ExecuteAtomically"または "ExecuteLocked"を使用して、ロックを保持しているコンテキストで評価したいコードのチャンクを含むラムダ式を渡すことができます。私はBoost/C++でそれを行う最善の方法を見つけ出そうとしていますが、私よりも賢い人がチャイムに入るかもしれません。 –

+0

@ChrisCleeland:これは問題について考えるとても興味深い方法です。 –

+0

もう少し考えてみるといいと思います。 "Execute"メソッドはFunctorを定義して第1引数に "this"(または* this)をバインドできるように、Execute(boost :: function )のようなシグネチャを持つことができます。それが適切に動作すると推測すると、boost :: lambda(または新しい言語機能)を使用してファンクタの本体を移動させて、コールがある場所にあるようにすることはあまり多くの作業であってはなりません実行する()。私は現在の仕事リストの中でこれを試してみる場所を見つけ出すことができたと思っていましたが、私はそうしていませんでした。後で試してみてください。 –

答えて

10

どうすればいいですか?

class t_scope_lock { 
public: 
    t_scope_lock(MyClass& myClass); 
    ... 
private: 
    boost::scoped_lock d_lock; 
}; 

とこのタイプのミューテックスへのアクセスを許可するためのMyClass

代替1

一つのアプローチは、boost::scoped_lockを有する型を作成することであろう。このクラスが具体的にMyClassのために書かれている場合は、内部クラスMyClass::t_scoped_lockとして追加するだけです。

代替2

別のアプローチは、(カスタム)スコープロックのコンストラクタに変換することができスコープロックとともに使用するための中間体タイプを作成することであろう。次に、タイプは適合しているようにオプトインできます。多くの人がカスタムスコープロックを気に入らないかもしれませんが、あなたが望むように簡単にアクセスを指定し、適切な制御を行うことができます。

第三の選択

時にはそれがMyClassのための抽象化レイヤを追加する方が良いでしょう。

{ 
boost::scoped_lock lock(f->GetScopedLock()); 
f->LockedFunc1(); 
f->LockedFunc2(); 
} 

(4

は時々あなたが別のロックを使用することができます代替例:クラスが複雑な場合、これはあなたがどのように見えるの変種の多くを提供する必要がありますので、良い解決策はそうではありません内部と外部)。

#4から5

同様の代替は、あなたには、いくつかのケースでは、再帰や読み書きロックを使用することができます。あなたが選択種類のインターフェースの部分へのアクセスを許可するためにロックされたラッパー型を使用することができます

オルタナティブ6

class MyClassLockedMutator : StackOnly { 
public: 
    MyClassLockedMutator(MyClass& myClass); 
// ... 
    void LockedFunc1() { this->myClass.LockedFunc1(); } 
    void LockedFunc2() { this->myClass.LockedFunc2(); } 
private: 
    MyClass& myClass; 
    boost::scoped_lock d_lock; // << locks myClass 
}; 

MyClass f; 
MyClassLockedMutator a(f); 

a.LockedFunc1(); 
a.LockedFunc2(); 

これは合理的ですか?

私はあなたのプログラムの厳密な制約が何であるか分かりません(したがって、複数の選択肢)。

代替案#1、#2、#3、および#6は、ほとんどの場合パフォーマンス上のオーバーヘッドがなく、余計な複雑さはほとんどありません。しかし、クライアントにとっては構文的に騒々しいです。 IMO、コンパイラが(必要に応じて)チェックできる強制的な正しさは、構文的ノイズを最小化するよりも重要です。

代替の#4と#5は、追加のオーバーヘッド/競合またはロック/同時エラーとバグを導入する良い方法です。場合によっては、それは考慮に値する単純な代替語です。

正確性、パフォーマンス、および/またはその他の制限が重要な場合は、構文的なノイズや抽象化レイヤーを要しても、その複雑さを抽象化またはカプセル化することは理にかなっていると思います。これは、プログラム全体を書いて管理していても、簡単に変更を導入することが容易であるためです。私にとっては、視認性のより精巧なケースであり、正しく使用されれば完全に賢明です。 mainまで

一部として

スクロール - このサンプルは、それが1にいくつかのアプローチを示しているのでかなり無秩序である:

#include <iostream> 
#include <boost/thread.hpp> 

class MyClass; 

class MyClassOperatorBase { 
public: 
    /* >> public interface */ 
    bool bazzie(bool foo); 
protected: 
    MyClassOperatorBase(MyClass& myClass) : d_myClass(myClass) { 
    } 

    virtual ~MyClassOperatorBase() { 
    } 

    operator boost::mutex &(); 

    MyClass& getMyClass() { 
     return this->d_myClass; 
    } 

    const MyClass& getMyClass() const { 
     return this->d_myClass; 
    } 

protected: 
    /* >> required overrides */ 
    virtual bool imp_bazzie(bool foo) = 0; 
private: 
    MyClass& d_myClass; 
private: 
    /* >> prohibited */ 
    MyClassOperatorBase(const MyClassOperatorBase&); 
    MyClassOperatorBase& operator=(const MyClassOperatorBase&); 
}; 

class MyClass { 
public: 
    MyClass() : mLock() { 
    } 

    virtual ~MyClass() { 
    } 

    void LockedFunc1() { 
     std::cout << "hello "; 
    } 

    void LockedFunc2() { 
     std::cout << "world\n"; 
    } 

    bool bizzle(bool foo) { 
     boost::mutex::scoped_lock lock(this->mLock); 

     return this->imp_bizzle(foo); 
    } 

protected: 
    virtual bool imp_bizzle(bool foo) { 
     /* would be pure virtual if we did not need to create it for other tests. */ 
     return foo; 
    } 

private: 
    class t_scope_lock { 
    public: 
     t_scope_lock(MyClass& myClass) : d_lock(myClass.mLock) { 
     } 

    private: 
     boost::mutex::scoped_lock d_lock; 
    }; 
protected: 
    friend class MyClassOperatorBase; 
private: 
    boost::mutex mLock; 
}; 

MyClassOperatorBase::operator boost::mutex &() { 
    return this->getMyClass().mLock; 
} 

bool MyClassOperatorBase::bazzie(bool foo) { 
    MyClass::t_scope_lock lock(this->getMyClass()); 

    return this->imp_bazzie(foo); 
} 

class TheirClassOperator : public MyClassOperatorBase { 
public: 
    TheirClassOperator(MyClass& myClass) : MyClassOperatorBase(myClass) { 
    } 

    virtual ~TheirClassOperator() { 
    } 

    bool baz(bool foo) { 
     boost::mutex::scoped_lock lock(*this); 

     return this->work(foo); 
    } 

    boost::mutex& evilClientMove() { 
     return *this; 
    } 

protected: 
    virtual bool imp_bazzie(bool foo) { 
     return this->work(foo); 
    } 

private: 
    bool work(bool foo) { 
     MyClass& m(this->getMyClass()); 

     m.LockedFunc1(); 
     m.LockedFunc2(); 
     return foo; 
    } 
}; 

class TheirClass : public MyClass { 
public: 
    TheirClass() : MyClass() { 
    } 

    virtual ~TheirClass() { 
    } 

protected: 
    virtual bool imp_bizzle(bool foo) { 
     std::cout << "hallo, welt!\n"; 
     return foo; 
    } 
}; 

namespace { 
/* attempt to restrict the lock's visibility to MyClassOperatorBase types. no virtual required: */ 
void ExampleA() { 
    MyClass my; 
    TheirClassOperator their(my); 

    their.baz(true); 

// boost::mutex::scoped_lock lock(my); << error inaccessible 
// boost::mutex::scoped_lock lock(my.mLock); << error inaccessible 
// boost::mutex::scoped_lock lock(their); << error inaccessible 

    boost::mutex::scoped_lock lock(their.evilClientMove()); 
} 

/* restrict the lock's visibility to MyClassOperatorBase and call through a virtual: */ 
void ExampleB() { 
    MyClass my; 
    TheirClassOperator their(my); 

    their.bazzie(true); 
} 

/* if they derive from my class, then life is simple: */ 
void ExampleC() { 
    TheirClass their; 

    their.bizzle(true); 
} 
} 

int main(int argc, const char* argv[]) { 
    ExampleA(); 
    ExampleB(); 
    ExampleC(); 
    return 0; 
} 
+0

私はその仕事をすることができるかどうかを見なければならないでしょう。 t_scope_lockコンストラクタがロックにどのようにアクセスするのかはわかりません。 T_scope_lockコンストラクタがロックを見つけることができるように、クラスは何らかの種類の 't_scope_lockable'クラスから降下する必要がありますか? (私は、呼び出し側が質問の中できれいな構文を取得した場合、クラス内でいくつかの醜さで生きることができます。) –

+0

また、可能であれば質問2に答えることができますか?なぜ誰もがこれをしないのですか?複数の操作で安定しなければならないクラスを操作するのは当然の方法です。 (アクセサーオブジェクトを取得します。情報XとYをチェックし、決定を下し、アクセサーを介してオブジェクトを変更します。完了し、ロックを解除します)。それとも誰もがこれをいくつかひどいやり方でやっていますか? –

+0

@David re Comment#1:私は応答を拡張しました(alt.4-6を追加しました)が、サンプルを生成します。コメント#2:申し訳ありませんが、最初の回答が更新される頃にそのビットを忘れていました。私は、正確さとスピードを保証するためにさらに長い時間をかけ、コンパイラーがいくつかのチェックを行うことを許可します(プログラムが大きすぎて自分自身を信頼するには速すぎます)。なぜ私はこれを頻繁に見ていないのか分かりません。おそらく、実装することを学ぶのに時間がかかり、クライアントが使うためです。私にとって、より複雑なケースを抽象化するのに要する時間は全体的に短くなります。 – justin

0

好ましい溶液は、このような原子の関数であろう。

void MyClass::Func_1_2(void) 
{ 
    boost::lock_guard<boost::mutex> lock(m_mutex); 
    LockedFunc1(); 
    LockedFunc2(); 
} 

これらの追加の方法のいくつかを提供する必要があります。原則:ユーザーからロックポリシーを隠す方がよいでしょうか。特別な方法を作成することが妥当ではないと分かった場合は、より高いレベルで抽象的なデザインを再考することができます。

インターフェイスを同じにする正当な理由がある場合は、ヘルパークラスの背後にあるロックの詳細を非表示にします。 2つの例。

ロックが必要なメソッドに渡されるトークンクラスの背後にあるロックを非表示にします。

MyClass my_class; 

{ 
    LockMyClass locked(my_class); 
    myclass.Func1(locked); // assert or throw if locked is not locking my_class 
    myclass.Func2(locked); 
} 

MyClassの友人でロックされたインタフェースクラスを作成します。

MyClass my_class; 

{ 
    LockedMyClass locked(my_class); 
    locked.Func1(); 
    locked.Func2(); 
} 

はこの賢明ですか?

注意が必要な場合は実行できますが、一般的には、クラス外で同期の詳細を公開する必要はありません。起こりうる問題は多すぎます。 Sunはjava.util.Vectorと同様の考えを試みましたが、以来、より良い技術に移行しました。

+0

コレクションを表すクラスがあり、そのクラスのすべてのクライアントがそのコレクションのメンバーに対して非常に複雑な操作を実行する必要がある場合(Xを検索、Yを検索、決定を下す、Xを変更する、Yを削除する、など)あなたはあまり並行性は必要ないので、コレクションのロックは1回で十分ですか? –

+0

あなたの説明は、ロックがコンテナの外に存在するように思えます。そう、はい、単一のロックが正しいと聞こえます。あるいは、プロセス全体を扱う 'MyClass :: make_decision(x、y)'コンテナにメソッドを追加することができます。 –

+0

さて、ロックはコンテナの外にあります。今、私は同じ問題を抱えています。ちょうど "コンテナ"は "ロックが存在する場所"になりました。クラスは、クラスの範囲外の情報への頻繁なアクセスを必要とするため、決定を下すことができません。 (低レベルの「オブジェクト状態」クラスと「オブジェクト状態のコレクション」クラスは、複数のオブジェクトの関連する状態を操作する多数の洗練された呼び出しクラスによって操作されると考えてください。呼び出し元が終了するまで) –

0

これは私が現在それを行う方法です。返すことができるScopedLockクラスを作成します。クラスを使用するには、boost::mutexが必要で、そのmutexで構築されたScopedLockが返されます。呼び出し元はこの関数を使用して独自のScopedLockを構築し、呼び出し元のScopedLockはクラスメンバー関数によって作成されたロックを継承します。

ScopedLockは、取得するためにあなたが呼び出したメンバ関数を持つクラスメンバの寿命を超えないため、ポインタは安全です。そして、(クラスの論理によって)1つのロック解除のみが保証されます。

私が見る唯一の本当の問題は、慎重な虐待であろう。たとえば、誰かがScopedLockから新しいScopedLockを構築した場合、他のScopedLock(長い人生を持つ可能性もあります)にはそれを持たないロックが継承されます。 (それをやると痛いです。)

class ScopedLock { 
private: 
    boost::mutex *mMutex; // parent object has greater scope, so guaranteed valid 
    mutable bool mValid; 

    ScopedLock();   // no implementation 

public: 
    ScopedLock(boost::mutex &mutex) : mMutex(&mutex), mValid(true) { 
     mMutex->lock(); 
    } 

    ~ScopedLock() { 
     if(mValid) mMutex->unlock(); 
    } 

    ScopedLock(const ScopedLock &sl) { 
     mMutex=sl.mMutex; 
     if(sl.mValid) 
     { 
       mValid=true; 
       sl.mValid=false; 
     } 
     else mValid=false; 
    } 

    ScopedLock &operator=(const ScopedLock &sl) 
    { // we inherit any lock the other class member had 
     if(mValid) mMutex->unlock(); 
     mMutex=sl.mMutex; 
     if(sl.mValid) { 
      mValid=true; 
      sl.mValid=false; 
     } 
    } 
}; 

それでも私には間違いがあります。 Boostのポイントは、あなたがする必要がある可能性が最も高いすべてのものに対してクリーンなインターフェースを提供することでした。これは非常に私にルーチンと思われる。そしてそれをするためのきれいな方法がないという事実が私を恐れている。

更新:これを行うための「正しい」ブースト方法は、ロックホルダーオブジェクトにshared_ptrを使用することです。オブジェクトは、最後のポインタが破棄されたときに消えてロックを解除します。

+0

これを行うための適切な方法を反映するためにあなたのサンプルを更新できますか?私はそれについて完全に明確ではない。 –

関連する問題