Облегченная обертка - это распространенная проблема, и если да, то как ее зовут? - PullRequest
13 голосов
/ 11 июля 2011

Мне нужно использовать библиотеку, которая делает вызовы базы данных не поточнобезопасными.Также мне иногда приходится загружать большие объемы данных в фоновом потоке.
Трудно сказать, какие библиотечные функции на самом деле обращаются к БД, поэтому я считаю, что самый безопасный для меня подход - это защитить каждый вызов библиотеки с помощью блокировки.

Допустим, у меня есть объект библиотеки:

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 Я думаю, это скорее идиома, чем шаблон).

Где мне искать дополнительную информацию?

Ответы [ 6 ]

8 голосов
/ 11 июля 2011

Вы можете сделать 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 вместо усиленных эквивалентов.

5 голосов
/ 11 июля 2011

В C ++ 0x вы можете реализовать некоторую форму декораторов:

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

использование:

dbLib::ErrorCode errorCode = protect([&]() 
{
    return someObject.someFunction(&items); 
});
4 голосов
/ 11 июля 2011

От ваше описание это может показаться работой для Pattern Decorator .

Однако, особенно в случае ресурсов, я бы не рекомендовал использовать его.

Причина в том, что в общем случае эти функции имеют тенденцию к плохому масштабированию, требуют более высокой (менее детальной) блокировки для согласованности или возвращают ссылки на внутренние структуры, которые требуют, чтобы блокировка оставалась заблокированной до тех пор, пока вся информация не будет прочитана.

Подумайте, например, о функции БД, которая вызывает хранимую процедуру, которая возвращает BLOB (поток) или курсор ref: потоки не должны читаться вне блокировки.

Что делать?

Я рекомендую вместо этого использовать Pattern Facade .Вместо того, чтобы составлять ваши операции непосредственно в терминах вызовов БД, реализуйте фасад, который использует слой БД;Затем этот уровень может управлять блокировкой точно на требуемом уровне (и оптимизировать, где это необходимо: вы можете реализовать фасад в виде локального потока Singleton и использовать отдельные ресурсы, исключая необходимость блокировок, например)

1 голос
/ 12 июля 2011

Эта статья Андрея Александреску содержит довольно интересную статью о том, как создать этот вид тонкой обертки и объединить его с опасным изменчивым ключевым словом для безопасности потоков.

1 голос
/ 11 июля 2011

Самое простое (и все еще простое) решение - написать функцию, которая возвращает прокси для объекта. Прокси-сервер выполняет блокировку и перегрузки ->, чтобы разрешить вызов объекта. Вот пример:

#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 дважды, хотя.

0 голосов
/ 11 июля 2011

Mutex блокировка - аналогичная проблема.Он попросил помощи здесь: Нужны некоторые отзывы о том, как сделать класс "потокобезопасным"

Решение, которое я придумал, - это класс-оболочка, предотвращающий доступ к защищенному объекту.Доступ может быть получен через класс «accessor».Аксессор заблокирует мьютекс в своем конструкторе и разблокирует его при уничтожении.См. Классы "ThreadSafe" и "Locker" в Threading.h для получения более подробной информации.

...