Hatred's Log Place

DON'T PANIC!

Dec 10, 2012 - 3 minute read - programming c++

Охранные классы в boost::thread

Для начала, охранные классы, это не что иное как различные *_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) и охранных классов. Кроме того, узнаете о условных переменных, функциях вызываемых единожды, реализации барьера.