我必须使用一个库来进行非线程安全的数据库调用。另外,我有时必须在后台线程中加载大量数据。
很难说哪些库函数实际上访问了数据库,所以我认为对我来说最安全的方法是用锁保护每个库调用。
假设我有一个库对象:
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 我我想,这更像是一种习语而不是一种模式)。
我必须在哪里寻找更多信息?
I have to use a library that makes database calls which are not thread-safe. Also I occasionally have to load larger amounts of data in a background thread.
It is hard to say which library functions actually access the DB, so I think the safest approach for me is to protect every library call with a lock.
Let's say I have a library object:
dbLib::SomeObject someObject;
Right now I can do something like this:
dbLib::ErrorCode errorCode = 0;
std::list<dbLib::Item> items;
{
DbLock dbLock;
errorCode = someObject.someFunction(&items);
} // dbLock goes out of scope
I would like to simplify that to something like this (or even simpler):
dbLib::ErrorCode errorCode =
protectedCall(someObject, &dbLib::SomeObject::someFunction(&items));
The main advantage of this would be that I won't have to duplicate the interface of dbLib::SomeObject
in order to protect each call with a lock.
I'm pretty sure that this is a common pattern/idiom but I don't know its name or what keywords to search for. (Looking at http://www.vincehuston.org/dp/gof_intents.html I think, it's more an idiom than a pattern).
Where do I have to look for more information?
发布评论
评论(6)
您可以将 protectedCall 设为一个模板函数,它采用不带参数的函子(意味着您将在调用站点绑定参数),然后创建作用域锁、调用函子并返回其值。例如:
您可以这样调用它:
EDIT。如果您使用的是 C++0x,则可以使用
std::function
和 < code>std::bind 而不是 boost 等价物。You could make protectedCall a template function that takes a functor without arguments (meaning you'd bind the arguments at the call-site), and then creates a scoped lock, calls the functor, and returns its value. For example something like:
You'd then call it like this:
EDIT. In case you're using C++0x, you can use
std::function
andstd::bind
instead of the boost equivalents.在 C++0x 中,您可以实现某种形式的装饰器:
用法:
In C++0x, you can implement some form of decorators:
usage:
从你的描述来看,这似乎是装饰器模式的工作。
但是,特别是在资源方面,我不建议使用它。
原因是,一般来说,这些函数往往扩展性很差,需要更高级别(不太细粒度)的锁定来保持一致性,或者返回对内部结构的引用,这些内部结构需要锁保持锁定状态,直到读取所有信息。
例如,考虑一个调用返回 BLOB(流)或引用游标的存储过程的 DB 函数:不应在锁之外读取流。
该怎么办?
我建议改用外观模式。不要直接根据数据库调用来编写操作,而是实现一个使用数据库层的外观;然后,该层可以在所需的级别上管理锁定(并在需要时进行优化:您可以将外观实现为线程本地单例,并使用单独的资源,从而消除对锁的需要,例如)
From your description this would seem a job for Decorator Pattern.
However, especially in the case of resources, I wouldn't recommend using it.
The reason is that in general these functions tend to scale badly, require higher level (less finegrained) locking for consistency, or return references to internal structures that require the lock to stay locked until all information is read.
Think, e.g. about a DB function that calls a stored procedure that returns a BLOB (stream) or a ref cursor: the streams should not be read outside of the lock.
What to do?
I recommend instead to use the Facade Pattern. Instead of composing your operations directly in terms of DB calls, implement a facade that uses the DB layer; This layer could then manage the locking at exactly the required level (and optimize where needed: you could have the facade be implemented as a thread-local Singleton, and use separate resources, obviating the need for locks, e.g.)
最简单(而且仍然直接)的解决方案可能是编写一个返回对象代理的函数。代理执行锁定和重载 ->允许调用该对象。这是一个示例:
下面是如何使用它:
输出应该是:
如果您希望锁定在参数求值之前发生,则可以使用此宏:
不过,这会计算 x 两次。
The simplest (and still straightforward) solution might be to write a function which returns a proxy for the object. The proxy does the locking and overloads -> to allow calling the object. Here is an example:
Here's how to use it:
The output should be:
If you want the lock to happen before the evaluation of the arguments, then can use this macro:
This evaluates
x
twice though.Andrei Alexandrescu 的这篇文章有一篇非常有趣的文章如何创建这种薄包装纸和将其与可怕的 volatile 关键字结合起来以确保线程安全。
This article by Andrei Alexandrescu has a pretty interesting article how to create this kind of thin wrapper and combine it with dreaded volatile keyword for thread safety.
互斥锁是一个类似的问题。它在这里寻求帮助:需要一些反馈如何使类“线程安全”
我想出的解决方案是一个包装类,它可以防止访问受保护的对象。可以通过“accessor”类获得访问权限。访问器将在其构造函数中锁定互斥锁,并在销毁时将其解锁。请参阅 Threading.h 了解更多详细信息。
Mutex locking is a similar problem. It asked for help here: Need some feedback on how to make a class "thread-safe"
The solution I came up with was a wrapper class that prevents access to the protected object. Access can be obtained via an "accessor" class. The accessor will lock the mutex in its constructor and unlock it on destruction. See the "ThreadSafe" and "Locker" classes in Threading.h for more details.