我必须使用一个库来进行非线程安全的数据库调用。另外,我偶尔也需要在后台线程中加载大量数据 很难说哪些库函数实际访问了DB,所以我认为对我来说最安全的方法就是用锁来保护每个库调用。
假设我有一个库对象:
dbLib::SomeObject someObject;
现在我可以这样做:
dbLib::ErrorCode errorCode = 0;
std::list<dbLib::Item> items;
{
DbLock dbLock;
errorCode = someObject.someFunction(&items);
} // dbLock goes out of scope
我想将其简化为类似的东西(甚至更简单):
dbLib::ErrorCode errorCode =
protectedCall(someObject, &dbLib::SomeObject::someFunction(&items));
这样做的主要优点是我不必复制dbLib::SomeObject
的接口,以便用锁来保护每个电话。
我很确定这是一种常见的模式/习语,但我不知道它的名字或要搜索的关键字。 (看http://www.vincehuston.org/dp/gof_intents.html我认为,这更像是一种成语而不是一种模式。)
我在哪里可以查找更多信息?
答案 0 :(得分:8)
你可以使protectedCall成为一个模板函数,它接受一个没有参数的函子(意味着你将绑定调用站点的参数),然后创建一个作用域锁,调用函子,并返回它的值。例如:
template <typename Ret>
Ret protectedCall(boost::function<Ret ()> func)
{
DbLock lock;
return func();
}
然后你会这样称呼它:
dbLib::ErrorCode errorCode = protectedCall(boost::bind(&dbLib::SomeObject::someFunction, &items));
编辑。如果您使用的是C ++ 0x,则可以使用std::function
和std::bind
代替提升等值。
答案 1 :(得分:5)
在C ++ 0x中,您可以实现某种形式的装饰器:
template <typename F>
auto protect(F&& f) -> decltype(f())
{
DbLock lock;
return f();
}
用法:
dbLib::ErrorCode errorCode = protect([&]()
{
return someObject.someFunction(&items);
});
答案 2 :(得分:4)
从您的描述这似乎是Decorator Pattern的工作。
但是,特别是在资源方面,我不建议使用它。
原因在于,一般来说,这些函数往往会严重缩放,需要更高级别(更少细粒度)的锁定以保持一致性,或者返回对内部结构的引用,这些内部结构要求锁定保持锁定直到读取所有信息。
想想,例如关于调用返回BLOB(流)或引用游标的存储过程的DB函数:不应该在锁之外读取流。
我建议改为使用Facade Pattern。而不是直接在数据库调用方面编写操作,而是实现使用数据库层的外观;然后,该层可以完全按照所需级别管理锁定(并在需要的地方进行优化:您可以将外观实现为线程本地Singleton,并使用单独的资源,从而避免需要锁定,例如。)
答案 3 :(得分:1)
最简单(也很简单)的解决方案可能是编写一个返回对象代理的函数。代理执行锁定和重载 - &gt;允许调用该对象。这是一个例子:
#include <cstdio>
template<class T>
class call_proxy
{
T &item;
public:
call_proxy(T &t) : item(t) { puts("LOCK"); }
T *operator -> () { return &item; }
~call_proxy() { puts("UNLOCK"); }
};
template<class T>
call_proxy<T> protect(T &t)
{
return call_proxy<T>(t);
}
以下是如何使用它:
class Intf
{
public:
void function()
{
puts("foo");
}
};
int main()
{
Intf a;
protect(a)->function();
}
输出应为:
LOCK
foo
UNLOCK
如果你想在评估参数之前发生锁定,那么可以使用这个宏:
#define PCALL(X,APPL) (protect(X), (X).APPL)
PCALL(x,x.function());
这会评估x
两次。
答案 4 :(得分:1)
This article有一篇非常有趣的文章,介绍如何创建这种瘦包装,并将其与可怕的volatile关键字相结合,以保证线程安全。
答案 5 :(得分:0)
互斥锁定是一个类似的问题。它在这里寻求帮助:Need some feedback on how to make a class "thread-safe"
我想出的解决方案是一个阻止访问受保护对象的包装类。可以通过“访问者”类获得访问权限。访问器将锁定构造函数中的互斥锁,并在销毁时将其解锁。有关详细信息,请参阅Threading.h中的“ThreadSafe”和“Locker”类。