Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 45
3.2.3. Выявление состояний гонки, внутренне присущих интерфейсам
Тот факт, что вы пользуетесь мьютексами или другим механизмом для защиты разделяемых данных, еще не означает, что гонок можно не опасаться, — следить за тем, чтобы данные были защищены, все равно нужно. Вернемся снова к примеру двусвязного списка. Чтобы поток мог безопасно удалить узел, необходимо предотвратить одновременный доступ к трем узлам: удаляемому и двум узлам но обе стороны от него. Заблокировав одновременный доступ к указателям на каждый узел но отдельности, мы не достигнем ничего по сравнению с вариантом, где мьютексы вообще не используются, поскольку гонка по-прежнему возможна. Защищать нужно не отдельные узлы на каждом шаге, а структуру данных в целом на все время выполнения операции удаления. Простейшее решение в данном случае — завести один мьютекс, который будет защищать весь список, как в листинге 3.1.
Однако и после обеспечения безопасности отдельных операций наши неприятности еще не закончились — гонки все еще возможны, даже для самого простого интерфейса. Рассмотрим структуру данных для реализации стека, например, адаптер контейнера >std::stack
, показанный в листинге 3.3. Помимо конструкторов и функции >swap()
, имеется еще пять операций со стеком: >push()
заталкивает в стек новый элемент, >pop()
выталкивает элемент из стека, >top()
возвращает элемент, находящийся на вершине стека, >empty()
проверяет, пуст ли стек, и >size()
возвращает размер стека. Если изменить >top()
, так чтобы она возвращала копию, а не ссылку (в соответствии с рекомендацией из раздела 3.2.2), и защитить внутренние данные мьютексом, то и тогда интерфейс уязвим для гонки. Проблема не в реализации на основе мьютексов, она присуща самому интерфейсу, то есть гонка может возникать даже в реализации без блокировок.
Листинг 3.3. Интерфейс адаптера контейнера >std::stack
>template
>class stack {
>public:
> explicit stack(const Container&);
> explicit stack(Container&& = Container());
> template
> template
> template
> template
> bool empty() const;
> size_t size() const;
> T& top();
> T const& top() const;
> void push(T const&);
> void push(T&&);
> void pop();
> void swap(stack&&);
>};
Проблема в том, что на результаты, возвращенные функциями