Все-таки параллельное программирование представляет собой сложную тему, и в одном рецепте нельзя отразить все применяемые в этой технологии методы. Можно использовать много шаблонов проектирования и разных стратегий, подходящих для различных приложений. Если при проектировании программного обеспечения вы предполагаете, что многопоточная обработка составит значительный объем, или проектируете высокопроизводительные приложения, необходимо прочитать хорошую книгу по шаблонам многопоточной обработки. Многие проблемы, связанные с трудностями отладки многопоточных программ, могут быть успешно преодолены за счет тщательного и продолжительного проектирования.
Использование мьютексов
Концепция мьютекса проста: мьютекс это некий объект, представляющий ресурс; только один поток может его блокировать или разблокировать в данный момент времени. Он является флагом, который используется для координации доступа к ресурсу со стороны нескольких пользователей. В библиотеке 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несколькими потоками. (В разных операционных системах это осуществляется по-разному, и именно поэтому не просто реализовать переносимую библиотеку потоков.)
Объекты mutexблокируются и разблокируются, используя несколько различных стратегий блокировки, самой простой из которых является блокировка scoped_lock. scoped_lock— это класс, при конструировании объекта которого используется аргумент типа mutex, блокируемый до тех пор, пока не будет уничтожена блокировка lock. Рассмотрим функцию-член enqueueв примере 12.2, которая показывает, как scoped_lockработает совместно с мьютексом mutex_.
void enqueue(const T& x) {
boost::mutex::scoped_lock lock(mutex_);
list_.push_back(x);
} // разблокировано!
Когда lockуничтожается, mutex_разблокируется. Если lockконструируется для объекта mutex, который уже заблокирован другим потоком, текущий поток переходит в состояние ожидания до тех пор, пока lockне окажется доступен.
Такой подход поначалу может показаться немного странным: а почему бы мьютексу mutexне иметь методы lockи unlock? Применение класса scoped_lock, который обеспечивает блокировку при конструировании и разблокировку при уничтожении, на самом деле более удобно и менее подвержено ошибкам. Когда вы создаете блокировку, используя scoped_lock, мьютекс блокируется на весь период существования объекта scoped_lock, т.е. вам не надо ничего разблокировать в явной форме на каждой ветви вычислений. С другой стороны, если вам приходится явно разблокировать захваченный мьютекс, необходимо гарантировать перехват любых исключений, которые могут быть выброшены в вашей функции (или где-нибудь выше ее в стеке вызовов), и гарантировать разблокировку mutex. При использовании scoped_lock, если выбрасывается исключение или функция возвращает управление, объект scoped_lockавтоматически уничтожается и mutexразблокируется.
Использование мьютекса позволяет сделать всю работу, однако хочется немного большего. При таком подходе нет различия между чтением и записью, что существенно, так как неэффективно заставлять потоки ждать в очереди доступа к ресурсу, когда многие из них выполняют только операции чтения, для которых не требуется монопольный доступ. Для этого в библиотеке Boost Threads предусмотрен класс read_write_mutex. Пример 12.3 показывает, как можно реализовать пример 12.2, используя read_write_mutexс функцией-членом front, которая позволяет вызывающей программе получить копию первого элемента очереди без его выталкивания.
Читать дальше