[英]How can I return a scoped lock?
比如说,考虑一组帐户余额。 然后你有一个复杂的功能,需要检查几个不同帐户的余额,然后调整几个不同帐户的余额。 对于集合的其他用户,操作需要是原子的。 你有一个集合类,其主要工作是提供这种原子性。 什么是'正确'的方式?
我有一个有一个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();
}
这个想法是LockedFunc1
和LockedFunc2
将被锁定调用。 用于lock
的析构函数将解锁f->mLock
。
我有两个基本问题:
1)我该怎么做?
2)这是明智的吗?
注意:这与这个类似命名的问题完全不同: 返回一个boost :: scoped_lock 。
备选方案1
一种方法是创建一个具有boost::scoped_lock
的类型:
class t_scope_lock {
public:
t_scope_lock(MyClass& myClass);
...
private:
boost::scoped_lock d_lock;
};
并为MyClass
授予对此类型的互斥锁的访问权限。 如果这个类是专门为MyClass
编写的,那么我只是将它添加为内部类MyClass::t_scoped_lock
。
备选方案2
另一种方法是创建一个与范围锁一起使用的中间类型,它可以转换为(自定义)范围锁的构造函数。 然后类型可以选择他们认为合适的。 很多人可能不喜欢自定义范围锁定,但它允许您根据需要轻松指定访问权限,并且具有良好的控制能力。
备选方案3
有时最好为MyClass
添加一个抽象层。 如果类是复杂的,这可能不是一个好的解决方案,因为你需要提供许多变体,如下所示:
{
boost::scoped_lock lock(f->GetScopedLock());
f->LockedFunc1();
f->LockedFunc2();
}
备选方案4
有时您可以使用另一个锁(例如内部和外部)。
备选方案5
与#4类似,在某些情况下可以使用递归锁或读写锁。
备选方案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
- 此示例相当混乱,因为它在一个示例中演示了几种方法:
#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;
}
首选的解决方案是像这样的原子函数:
void MyClass::Func_1_2( void )
{
boost::lock_guard<boost::mutex> lock( m_mutex );
LockedFunc1();
LockedFunc2();
}
您可能必须提供其中一些额外的方法。 原则:最好是隐藏用户的锁定策略。 如果您发现创建特殊方法是不合理的,您可能需要重新考虑您的设计,从更高层次抽象。
如果您有合理的理由保持接口相同,请隐藏辅助类后面的锁定详细信息。 两个例子。
隐藏传递给需要锁定的方法的标记类后面的锁。
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
类似的想法,但后来转向了更好的技术。
这就是我目前计划这样做的方式。 我将创建一个可以返回的ScopedLock
类。 要使用它,类必须有一个boost::mutex
并返回使用该互斥锁构造的ScopedLock
。 调用者使用该函数构造自己的ScopedLock,调用者的ScopedLock继承由类成员函数创建的锁。
指针是安全的,因为ScopedLock不能超过您调用其成员函数来获取它的类成员的生命周期。 你可以保证(通过班级的逻辑)只有一个解锁。
我看到的唯一真正的问题是故意滥用。 例如,如果有人从他们的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的重点是为你最有可能需要做的所有事情提供一个干净的界面。 这对我来说似乎很常见。 事实上没有干净的方法让我感到害怕。
更新 :执行此操作的“正确”Boost方法是将shared_ptr
用于锁定持有者对象。 当最后一个指针被销毁时,该对象将消失,释放锁定。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.