1

这主要是一个设计问题,但由于我使用 C++11 编程,我更喜欢使用 C++11 的解决方案/建议。

基本上问题是我想让我的类易于维护,同时避免使用过度锁定。

问题很简单:

如果仅由在开始时锁定类互斥锁的类函数调用它,我可以避免在类函数的开头private锁定。维护地狱是,将来很难看到什么方法正在调用什么,并且有可能有人会将 b 公开,并使类线程不安全。apublicb

那么最佳解决方案是什么:

  • 评论b说它没有锁定std::mutex,因为它只被调用a
  • 使用recursive_mutex并锁定每种方法:似乎很浪费
  • 别的东西
4

3 回答 3

3

有可能有人会b公开

您不必担心这一点。如果有人在不了解后果或如何正确使用该功能的情况下将私有实现细节变为公共成员,那么您已经有维护的噩梦了。您是否还担心他们会公开您的所有数据成员?担心这些事情似乎是在浪费时间。如果不明显该函数不是公开的,那么重命名它并添加清晰的注释。

然而 ...

经常使用的一种替代方法是要求将锁定对象传递给函数,这样在不锁定互斥锁的情况下更难调用它,无论该锁定是由另一个成员函数还是任何其他类完成:

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

最简单的方法是拥有一个执行该工作但没有互斥体成员的实现类,以及一个包含实现类和互斥体的包装类。然后包装器将互斥锁锁定在每个成员函数中,然后调用实现类。如果实现调用自身的另一个成员函数,则它知道互斥锁始终处于锁定状态。由于包装器成员函数只是简单的包装器,因此很容易验证它们始终锁定互斥锁。

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 回答