, определенный в boost/thread/mutex.hpp, для синхронизации доступа к потокам. Пример 12.2 показывает, как можно использовать в простых случаях объект
mutex
для управления параллельным доступом к очереди.
Пример 12.2. Создание потокозащищенного класса
#include <iostream>
#include <boost/thread/thread.hpp>
#include <string>
// Простой класс очереди; в реальной программе вместо него
следует
// использовать std::queue
template<typename T>
class Queue {
public:
Queue {}
~Queue {}
void enqueue(const T& x) {
// Блокировать мьютекс для этой очереди
boost::mutex::scoped_lock lock(mutex_);
list_.push_back(x);
// scoped_lock автоматически уничтожается (и, следовательно, мьютекс
// разблокируется) при выходе из области видимости
}
T dequeue {
boost::mutex::scoped_lock lock(mutex_);
if (list_.empty)
throw "empty!"; // Это приводит к выходу из текущей области
T tmp = list_.front; // видимости, поэтому блокировка освобождается
list_.pop_front;
return(tmp);
} // Снова при выходе из области видимости мьютекс разблокируется
private:
std::list<T> list_;
boost::mutex mutex_;
};
Queue<std::string> queueOfStrings;
void sendSomething {
std::string s;
for (int i = 0; i < 10; ++i) {
queueOfStrings.enqueue("Cyrus");
}
}
void recvSomething {
std::string s;
for(int i = 0; i < 10; ++i) {
try {
s = queueOfStrings.dequeue;
} catch(...) {}
}
}
int main {
boost::thread thr1(sendSomething);
boost::thread thr2(recvSomething);
thr1.join;
thr2.join;
}
Обсуждение
Обеспечение потокозащищенности классов, функций, блоков программного кода и других объектов является сущностью многопоточного программирования. Если вы проектируете какой-нибудь компонент программного обеспечения с возможностями многопоточной обработки, то можете постараться обеспечить каждый поток своим набором ресурсов, например объектами в стеке и динамической памяти, ресурсами операционной системы и т.д. Однако рано или поздно вам придется
обеспечить совместное использование различными потоками каких-либо ресурсов. Это может быть совместная очередь поступающих запросов (как это происходит на многопоточном веб-сервере) или нечто достаточно простое, как поток вывода (например, в файл журнала или даже в
cout
). Стандартный способ координации безопасного совместного использования ресурсов подразумевает применение мьютекса (mutex), который обеспечивает монопольный доступ к чему-либо.
Остальная часть обсуждения в целом посвящена мьютексам, и в частности методам использования
boost::mutex
для сериализации доступа к ресурсам. Я использую терминологию подхода «концепция/модель», о котором я говорил кратко во введении настоящей главы. Концепция — это абстрактное (независимое от языка) описание чего-либо, а модель концепции — конкретное ее представление в форме класса С++. Уточнение концепции — это определенная концепция с некоторыми дополнительными возможностями.
Все-таки параллельное программирование представляет собой сложную тему, и в одном рецепте нельзя отразить все применяемые в этой технологии методы. Можно использовать много шаблонов проектирования и разных стратегий, подходящих для различных приложений. Если при проектировании программного обеспечения вы предполагаете, что многопоточная обработка составит значительный объем, или проектируете высокопроизводительные приложения, необходимо прочитать хорошую книгу по шаблонам многопоточной обработки. Многие проблемы, связанные с трудностями отладки многопоточных программ, могут быть успешно преодолены за счет тщательного и продолжительного проектирования.
Использование мьютексов
Концепция мьютекса проста: мьютекс это некий объект, представляющий ресурс; только один поток может его блокировать или разблокировать в данный момент времени. Он является флагом, который используется для координации доступа к ресурсу со стороны нескольких пользователей. В библиотеке Boost Threads моделью концепции мьютекса является класс
boost::mutex
. В примере 1 2.2 доступ для записи в классе Queue обеспечивается переменной-членом
mutex
.
boost::mutex mutex_;
mutex_
должен блокироваться какой-нибудь функцией-членом, которая должна изменять состояние очереди обслуживаемых элементов. Сам объект
mutex_
ничего не знает о том, что он представляет. Это просто флаг блокировки/разблокировки, используемый всеми пользователями некоторого ресурса.
В примере 12.2, когда какая-нибудь функция-член класса
Queue
собирается изменить состояние объекта, она сначала должна заблокировать
mutex_
. Только один поток в конкретный момент времени может его заблокировать, что не позволяет нескольким объектам одновременно модифицировать состояние объекта
Queue
. Таким образом, мьютекс
mutex
представляет собой простой сигнальный механизм, но это нечто большее, чем просто
bool
или
int
, потому что для mutex необходим сериализованный доступ, который может быть обеспечен только ядром операционной системы. Если вы попытаетесь сделать то же самое с
bool
, это не сработает, потому что ничто не препятствует одновременной модификации состояния
bool
несколькими потоками. (В разных операционных системах это осуществляется по-разному, и именно поэтому не просто реализовать переносимую библиотеку потоков.)