Для начала, охранные классы, это не что иное как различные *_lock
классы, реализующие идиому
RAII, захватывающие мутекс в конструкторе (lock()
) и освобождающие его в деструкторе (unlock()
) -
паттерн “блокировка в области видимости”. Не смог придумать более корректного перевода.
Охранных классов в Boost много, легко можно в них запутаться. Но знать про них необходимо, так как почти вся библиотека boost::thread спроектирована с ориентацией на их использование.
boost::lock_guard
Самый простой охранный класc. Не предоставляет никакой дополнительной функциональности кроме захвата мутекса в конструкторе и освобождение в деструкторе. Используется для защиты данных в пределах какой-то области видимости:
class Foo
{
public:
void increment()
{
boost::lock_guard<boost::mutex> lock(mutex);
++data;
}
void decrement()
{
boost::lock_guard<boost::mutex> lock(mutex);
--data;
}
int value()
{
boost::lock_guard<boost::mutex> lock(mutex);
return data;
}
private:
boost::mutex mutex;
int data;
};
boost::unique_lock
Более навороченный класс-защитник. Помимо захвата и освобождения мутекса предоставляет доступ почти ко всем его методам:
lock()
try_lock()
timed_lock()
unlock()
Кроме того предоставляет различные варианты конструирования.
Благодаря своей возможности освобождать и снова захватывать мутекс в процессе своей жизни может использоваться совместно с
boost::conditional_variable
и boost::conditional_variable_any
Самый простой пример использования аналогичен boost::lock_guard
, но более интерес вариант использования с условными переменными:
class Foo
{
public:
void push(int value)
{
boost::lock_guard<boost::mutex> lock(mutex); // Защитим данные
data = value; // запишем данные
cond.notify_all(); // уведомим, что есть новые данные
}
int pop()
{
boost::unique_lock<boost::mutex> lock(mutex); // Защитим данные
cond.wait(lock); // ждём новые данные, при этом мутекс разблокируется, по выходу
// он будет снова заблакирован
return data;
}
private:
boost::mutex mutex;
boost::conditional_variable cond;
int data;
};
Хоть к теме данной заметки это не относится, но обращу внимание, что cond.wait()
может быть прерван до вызова notify_all()
или
notify_one()
, поэтому в реальном коде нужно применять какие-то дополнительные меры проверки, обычно используется такую конструкцию:
boost::scoped_lock<boost::mutex> lock(mutex);
while (isNewDataAvail)
{
cond.wait(lock);
}
boost::mutex::scoped_lock
Да, это не опечатка, boost::mutex::scoped_lock
является всего лишь объявлением типа, вида:
typedef boost::unique_lock<boost::mutex> scoped_lock;
внутри класса boost::mutex
и предоставляет более короткий вариант объявления:
boost::mutex::scoped_lock lock(mutex);
Хотя это и спорно.
boost::shared_lock
shared_lock
похож на boost::unique_lock
за тем малым исключением что работает с boost::shared_mutex
, то есть тем типом мутекса, который
реализует подход “много читателей/один писатель” и используется в той части где происходит чтение. Таким образом, shared_lock
вызывает методы lock_shared()
и unlock_shared()
у переданного ему мутекса вместо lock()
и unlock()
и не будет заблокирован до тех пор, пока где-то
не будет вызван lock()
.
К слову сказать, boost::unique_lock
так же работает с boost::shared_mutex
, но используется со стороны писателя (аналогично может быть использован и boost::lock_guard
).
boost::upgrade_lock
Аналогичен boost::shared_lock
, используется совместно с boost::upgrade_mutex
(на pthread-системах это синоним для boost::shared_mutex
) используется в системах в которых реализуется подход “много читателей/один писатель” в случае, когда нужно превратиться из читателя в писателя (проапгрейдится)
boost::upgrade_to_unique_lock
Используется совместно с boost::upgrade_lock
для временного повышения своих прав владения до эксклюзивных, тем становясь в схеме “много читателей/один писатель” из читателя писателем.
void foo()
{
boost::upgrade_lock<boost::shared_mutex> lock(mutex);
...
// что-то делаем
...
if (/* какое-то условие */)
{
// становимся писателем
boost::upgrade_to_unique_lock<boost::shared_mutex> newLock(lock);
...
// пишем
...
}
// а здесь мы снова читатели
...
}
Мутекс-специфичный scoped_try_lock
Объявляется как typedef внутри класса мутекса, в частности: boost::mutex::scoped_try_lock
. Всё отличие от boost::unique_lock<MutexType>
заключается в том, что в конструкторе вызывается try_lock
вместо lock
.
Немного итогов
Несомненно, самым часто используем охранным классом является boost::lock_guard
, так как реализует необходимый минимум функционала при минимуме
накладных расходов. Затем идёт boost::unique_lock
или его синоним для boost::mutex
- boost::mutex::scoped_lock
. Остальные классы применяются гораздо реже.
Разуется тут не отражено всего, что может быть в текущей версии библиотеки, поэтому рекомендую смотреть полную информацию по следующей ссылке:
http://www.boost.org/doc/libs/1_52_0/doc/html/thread/synchronization.html
подставляя вместо номера версии нужную вам.
Там узнаете о различных подходах к созданию функционала блокировок, а так же вариантах их реализации в виде конкретных классов мутексов (boost::mutex
, boost::shared_mutex
) и охранных классов. Кроме того, узнаете о условных переменных, функциях вызываемых единожды, реализации барьера.