class critical_section { CRITICAL_SECTION cs_; public: critical_section() { InitializeCriticalSection(&cs_); } ~critical_section() { DeleteCriticalSection(&cs_); } void enter() { EnterCriticalSection(&cs_); } void leave() { LeaveCriticalSection(&cs_); } }; template< class TLock, void (TLock::*en)() = &TLock::enter, void (TLock::*le)() = &TLock::leave > class scoped_lock { TLock& lock_; public: scoped_lock(TLock& lock) : lock_(lock) { (lock_.*en)(); } ~scoped_lock() { (lock_.*le)(); } }; typedef scoped_lock<critical_section> slock;Класс critical_section - критическая секция. Класс scoped_lock - реализует принцип RAII, для произвольного класса. Указатели на методы вынесены в параметры шаблона для того, что-бы можно было работать с более сложными объектами синхронизации, например реализующими read-write lock. Используется это очень просто:
critical_section lock_; void foo () { slock lockit(lock_); //этот участок кода //может выполняться только //одним потоком }в общем, эта идиома широко известна... Далее мне нужно просто использовать этот прием во всех не константных методах класса. Если так сделать, то объект класса сможет выступать в качестве общего ресурса для нескольких потоков. Но здесь есть один существенный минус. Это сделает вызовы методов нашего класса более дорогими, даже, если объект будет использоваться только одним потоком. Что-бы этого избежать, нужно иметь возможность отключать синхронизацию.
Самый простой способ этого добиться - использовать препроцессор, гибкость нулевая, зато работает =)
Более правильный способ - не хардкодить critical_section в качестве объекта синхронизации для нашего класса, а передавать его в качестве шаблонного параметра. Если нужно отключить блокировку, достаточно передать в шаблон класс - заглушку, с пустыми методами enter и leave. По сравнению с первым способом это очень гибко, можно в одном месте программы использовать потокобезопасную версию объекта, а в другой небезопасную, но более быструю. Но здесь то-же есть минус. Клиент нашего класса должен знать о том, что должен из себя представлять класс critical_section, это не очень хорошо...
Оптимальный вариант с точки зрения чистоты и ясности кода - передавать в шаблон логическое значение. Что-то вроде этого:
template <
bool enable_lock = false
>
class some_class...
если передали true, то использовать потокобезопасную версию объекта, если false - обычную.
Теперь о том как это реализовать:
template<class TLock, class TScopedLock, bool enabled>
struct switch_lock;
template<class TLock, class TScopedLock>
struct switch_lock<TLock, TScopedLock, true>
{
TScopedLock lock_it_;
switch_lock(TLock& lock) : lock_it_(lock) {}
};
template<class TLock, class TScopedLock>
struct switch_lock<TLock, TScopedLock, false>
{
switch_lock(TLock& lock) {}
};
Выглядит немного пугающе, но это всего-лишь враппер для scoped_lock, в качестве первого параметра он получает класс синхронизации, например critical_section, второй параметр - RAII враппер для первого параметра, например slock, ну и последний параметр отвечает за выбор реализации.
Сам класс будет выглядеть так:
template <
bool enable_lock = false
>
class some_class
{
typedef switch_lock<critical_section, slock, enable_lock> locker;
critical_section lock_;
public:
some_class()
{
std::cout << "ctor" << std::endl;
}
template<bool el>
some_class(const some_class<el>& el)
{
std::cout << "copy ctor" << std::endl;
}
template<bool el>
some_class& operator = (const some_class<el>& el)
{
std::cout << "assign" << std::endl;
return *this;
}
void some_function()
{
locker l(lock_);//лочим (захватываем мьютэкс, входим в критическую секцию, итд)
std::cout << "function called" << std::endl;
}
};
весь трюк состоит в том, что-бы вместо scoped_lock, использовать враппер, вот и все :)