开发者

Lightweight wrapper - is this a common problem and if yes, what is its name?

开发者 https://www.devze.com 2023-03-19 13:25 出处:网络
I have to use a library that makes database calls which are not thread-safe. Also I occa开发者_StackOverflow中文版sionally have to load larger amounts of data in a background thread.

I have to use a library that makes database calls which are not thread-safe. Also I occa开发者_StackOverflow中文版sionally 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?


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:

template <typename Ret>
Ret protectedCall(boost::function<Ret ()> func)
{
    DbLock lock;
    return func();
}

You'd then call it like this:

dbLib::ErrorCode errorCode = protectedCall(boost::bind(&dbLib::SomeObject::someFunction, &items));

EDIT. In case you're using C++0x, you can use std::function and std::bind instead of the boost equivalents.


In C++0x, you can implement some form of decorators:

template <typename F>
auto protect(F&& f) -> decltype(f())
{
    DbLock lock;
    return f();
}

usage:

dbLib::ErrorCode errorCode = protect([&]() 
{
    return someObject.someFunction(&items); 
});


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.)


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:

#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);
}

Here's how to use it:

class Intf
{
 public:
  void function() 
 {
  puts("foo");
 }
};

int main()
{
 Intf a; 
 protect(a)->function(); 
}

The output should be:

LOCK
foo
UNLOCK

If you want the lock to happen before the evaluation of the arguments, then can use this macro:

#define PCALL(X,APPL) (protect(X), (X).APPL)
PCALL(x,x.function());

This evaluates x twice though.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消