这是个好问题。将公共接口与实现分开以及将功能与多线程问题分开通常是个好主意。
我一直在使用递归互斥锁,然后我的方法名称带有和不带有“_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!
$
希望能帮助到你。