1

これは主に設計上の問題ですが、私は C++11 でプログラミングしているので、C++11 を使用するソリューション/アドバイスを好みます。

基本的な問題は、過度のロックの使用を避けながら、クラスを簡単に保守できるようにしたいということです。

問題は単純です:

開始時にクラスミューテックスをロックしたクラス関数によってのみ呼び出される場合、privateクラス関数の開始時にロックを回避できます。メンテナンス地獄は、将来、どのメソッドが何を呼び出しているかを確認するのが容易ではなく、誰かが単に b を公開して、クラス スレッドを安全でなくする可能性があることです。apublicb

それで、最適な解決策は何ですか:

  • によってのみ呼び出されるため、bロックしないとコメントするstd::mutexa
  • recursive_mutexすべてのメソッドを使用してロックする: 無駄に思える
  • 他の何か
4

3 に答える 3

3

誰かがb公表する可能性がある

それについて心配する必要はありません。結果や関数の正しい使用方法を理解せずに、誰かが非公開の実装の詳細を公開メンバーに作成した場合、メンテナンスの悪夢が既に発生しています。また、すべてのデータ メンバーが公開されることを心配していますか? そんなことを気にするのは時間の無駄に思えます。関数がパブリックであることを意図していないことが明らかでない場合は、名前を変更し、明確なコメントを追加してください。

でも ...

よく使用される代替手段の 1 つは、ロック オブジェクトを関数に渡すことを要求することです。これにより、ロックが別のメンバー関数または他のクラスによって行われるかどうかにかかわらず、mutex をロックせずに呼び出すことが難しくなります。

class X
{
public:
    void frobnicate()
    {
        std::unique_lock<std::mutex> lock(this->mutex);
        frob_impl(lock);
    }

private:
    void frob_impl(const std::unique_lock<std::mutex>&)
    {
        // do it
    }

    std::mutex mutex;
};

愚かなことをするためにコードを変更する人々に対して本当に偏執的になりたい場合は、正しいミューテックスがロックされていることを確認することを追加できます。

    void frob_impl(const std::unique_lock<std::mutex>& lock)
    {
        assert( &this->mutex == lock.mutex() );
        // do it
    }

しかし、後のメンテナンス プログラマーを信用できない場合、彼らがそのチェックを削除しないとどうしてわかるでしょうか?

于 2012-10-14T23:28:36.587 に答える
2

これを行う最も簡単な方法は、作業を行うがミューテックス メンバーを持たない実装クラスと、実装クラスとミューテックスを保持するラッパー クラスを用意することです。次に、ラッパーはすべてのメンバー関数でミューテックスをロックし、実装クラスを呼び出します。実装がそれ自体の別のメンバー関数を呼び出す場合、mutex が常にロックされていることがわかります。ラッパー メンバー関数は単純なラッパーであるため、常にミューテックスをロックしていることを簡単に確認できます。

class X{
    class Impl{
    private:
        int i,j;
        int bar(){ return i;}
        int baz(){ return j;}
    public:
        Impl():i(36),j(6){}
        int foo(){
            return bar()+baz();
        }
    };

    Impl impl;
    std::mutex m;
public:
    int foo(){
        std::lock_guard<std::mutex> guard(m);
        return impl.foo();
    }
};
于 2012-10-15T09:03:36.963 に答える
1

それは良い質問です。通常は、パブリック インターフェイスを実装から分離し、機能をマルチスレッドの問題から分離することをお勧めします。

私は再帰ミューテックスを使用しており、「_locked」サフィックスなどを含むメソッド名と含まないメソッド名がありました。維持および拡張するのは依然として負担であり、毎回ロックするのは遅かったです(再帰ミューテックスも最悪です)、常にトレースする必要がありました一体何が起こっているのかを把握するためのデータ/呼び出しパス。デッドロックのデバッグは非常に簡単で、保護されていないアクセスの方がはるかに楽しいものでした。

最近では通常、ミューテックスを気にせずにクラスを実装し、「プロキシ」パターンとして実装された壁の後ろにクラスを隠すことで、「ロック解除」アクセスからクラスを保護しています。少なくとも数年間は私にとってはうまく機能していましたが、これまでのところ苦情は​​ありません. このコードはあなたにアイデアを与えるはずです:

#include <cstdio>
#include <mutex>
#include <utility>

class SomeClass {
  public:
    explicit SomeClass(int v) : v(v) {}

    void foo() { printf("\t\tCALLED foo(%d)\n", v); }
    void bar() { foo(); printf("\t\tCALL bar(%d)\n", v); }

  private:
    int v;
};

template <typename T>
class Protector {
    std::mutex m_;
    T          c_;
  public:
    template <typename ...Args>
    Protector(Args && ...args)
        : m_(), c_(std::forward<Args>(args)...)
    {}

    class Interface {
        Protector *p_;

        Interface(const Interface &) = delete;
        Interface & operator = (const Interface &) = delete;

      public:
        Interface(Protector *p) : p_(p) {
            printf("\t+++++ Lock! +++++\n");
            p_->m_.lock();
        }

        Interface(Interface && rhs) : p_(rhs.p_) { rhs.p_ = nullptr; }
        T *operator->() { return p_ ? &p_->c_ : nullptr; }
        ~Interface() {
            if (p_) {
                printf("\t----- Unlock! -----\n");
                p_->m_.unlock();
            }
        }
    };

    Interface lock() { return Interface(this); }
    Interface operator ->() { return lock(); }

    Protector(const Protector &) = delete;
    Protector & operator = (const Protector &) = delete;
};

int main()
{
    Protector<SomeClass> p(12345);
    printf("--*-- Doing batch access! --*--\n");
    {
        auto c = p.lock();
        c->foo();
        c->bar();
    }
    printf("--*-- Doing silly access! --*--\n");
    p->foo();
    p->bar();
    printf("Done!\n");
}

実行例:

$ clang++ -std=c++11 -stdlib=libc++ -O4 -Wall -pedantic -o test ./test.cpp 
$ ./test 
--*-- Doing batch access! --*--
    +++++ Lock! +++++
        CALLED foo(12345)
        CALLED foo(12345)
        CALL bar(12345)
    ----- Unlock! -----
--*-- Doing silly access! --*--
    +++++ Lock! +++++
        CALLED foo(12345)
    ----- Unlock! -----
    +++++ Lock! +++++
        CALLED foo(12345)
        CALL bar(12345)
    ----- Unlock! -----
Done!
$ 

それが役に立てば幸い。

于 2012-10-14T23:41:53.657 に答える