воскресенье, 12 июня 2011 г.

boost::thread урок 2 Мьютексы

Любой, кто писал многопоточную программу, по-
нимает, насколько важно множеству потоков не обра-
щаться к одному и тому же разделяемому ресурсу в
одно и то же время. Если один поток пытается изме-
нить значение разделяемых данных в то время, когда
другой поток пытается это значение прочитать, ре-
зультатом оказывается неопределенное поведение.
Чтобы это предотвратить, используют некоторые спе-
циальные примитивные типы и операции. Самый фун-
даментальный из этих типов известен как мьютекс
(сокращение от «mutual exclusion» - взаимное исклю-
чение). Мьютекс позволяет получить доступ к разде-
ляемому ресурсу только одному потоку в один момент
времени, этот поток должен «заблокировать» мьютекс.
Если другой поток уже заблокировал мьютекс, то эта
операция ждет, пока мьютекс не будет освобожден
(«разблокирован»), таким образом гарантируется, что
только один поток имеет доступ к разделяемому ре-
сурсу в один момент времени.
Мьютекс может иметь несколько вариаций. Две
больших категории поддерживаемых библиотекой
Boost.Threads мьютексов включают простые и рекур-
сивные мьютексы. Простой мьютекс может быть за-
блокирован только один раз. Попытка повторного за-
хвата мьютекса приводит к тупику, что выражается в
бесконечном ожидании. При использовании рекурсив-
ного мьютекса один поток может заблокировать мью-
текс несколько раз и должен ровно столько же раз его
разблокировать, прежде чем другой поток получит
возможность его заблокировать.
В рамках этих двух больших категорий мьютексов
выделяют и другие способы блокирования мьютекса.
Поток может попытаться заблокировать мьютекс тре-
мя способами:
1) Попробовать заблокировать мьютекс, ожидая
пока ни один другой поток не будет им владеть.
2) Попробовать заблокировать мьютекс, немедлен-
но вернувшись, если этот мьютекс уже кем-то забло-
кирован.
3) Попробовать заблокировать мьютекс, ожидая
либо его освобождения другим потоком, либо истече-
ния указанного периода времени.
Представляется, что наилучшим возможным типом
мьютекса является рекурсивный мьютекс, поддержи-
вающий все три формы блокирования. Однако с каж-
дой вариацией добавляются накладные расходы, по-
этому библиотека Boost.Threads предусматривает воз-
можность выбора наиболее эффективного вида мью-
текса для каждой конкретного случая. Таким образом,
имеются шесть типов мьютексов, перечисленных здесь
в порядке снижения эффективности: boost::mutex,
boost::try_mutex,
boost::timed_mutex,
boost::recursive_mutex, boost::recursive_try_mutex, и
boost::recursive_timed_mutex.
Тупики могут случиться всякий раз, когда мьютекс
заблокирован и вовремя не освобожден. Это одна из
самых распространенных ошибок, поэтому библиотека
Boost.Threads спроектирована так, чтобы сделать ее
невозможной (или, по крайней мере, трудно реализуе-
мой). Нет ни одной непосредственной операции по
блокированию или разблокированию мьютекса. Вме-
сто этого классы мьютексов предоставляют определе-
ния типов (typedef), реализующих идиому RAII (Re-
source Acquisition is Initialization — Захват ресурса яв-
ляется инициализацией) для блокирования и разблоки-
рования мьютекса. Эта идея известна как паттерн
«Блокировка в области видимости» 4 (Scoped Lock).
При создании объекта такого типа ему передается
ссылка на мьютекс. Конструктор блокирует мьютекс, а
деструктор разблокирует его. Правила языка C++ га-
рантируют, что деструктор всегда будет вызван, так
что даже если выброшено исключение, мьютекс всегда
будет корректно разблокирован.
Этот паттерн помогает гарантировать правильное
использование мьютекса. Однако, нужно быть гото-
вым к тому, что хотя мьютекс окажется разблокиро-
ванным, не факт, что каждый разделяемый ресурс
окажется в корректном состоянии в случае возникно-
вения исключения. Поэтому, как и в случае с одним
потоком выполнения, следует всякий раз убеждаться в
том, что исключения не приводят к нарушению цело-
стности состояния программы. Кроме того, объекты
блокировок не должны передаваться другим потокам,
так как их состояние не защищено при таком исполь-
зовании.
В примере 2 показано очень простое использование
класса boost::mutex. Создаются два новых потока, каж-
дый из них 10 раз выводит свой id и счетчик цикла, а
основной поток дожидается их завершения. Объект
std::cout является разделяемым ресурсом, поэтому ка-
ждый поток использует глобальный мьютекс, гаранти-
рующий, что только один поток в каждый момент вре-
мени пытается осуществлять вывод.
// Пример 2
#include
#include
#include
boost::mutex io_mutex;
struct count
{
count(int id) : id(id) { }
void operator()()
{


for (int i = 0; i < 10; ++i) {
boost::mutex::scoped_lock lock(io_mutex);
std::cout << id << ": " << i << std::endl;
}
}
int id;
};
int main(int argc, char* argv[]) {
boost::thread thrd1(count(1));
boost::thread thrd2(count(2));
thrd1.join();
thrd2.join();
return 0;
}
Многие пользователи заметят, что передача данных потоку требует ручного кодирования функционально- го объекта. Хотя этот код и тривиален, писать его вся- кий раз довольно скучно. Есть и более простое реше- ние. Функциональные библиотеки позволяют создать новые функциональные объекты, связывая (bind) другие функциональные объекты с данными, которые при вызове будут им переданы. В примере 3 показано, как при использовании библиотеки Boost.Bind можно уп- ростить код примера 2, отказавшись от ручного коди- рования функционального объекта.
// Пример 3
// Эта программа идентична программе
// из примера 2, кроме того, что
// использует Boost.Bind
// при создании потока,
// принимающего параметры.
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>
boost::mutex io_mutex;
void count(int id)
{
for (int i = 0; i < 10; ++i)
{
boost::mutex::scoped_lock
lock(io_mutex);
std::cout << id << ": " <<
i << std::endl;
}
}
int main(int argc, char* argv[])
{
boost::thread thrd1(
boost::bind(&count, 1));
boost::thread thrd2(
boost::bind(&count, 2));
thrd1.join();
thrd2.join();
return 0;
}

Комментариев нет:

Отправить комментарий