Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 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 explicit stack(const Alloc&);

> template stack(const Container&, const Alloc&);

> template stack(Container&&, const Alloc&);

> template stack(stack&&, const Alloc&);

> 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&&);

>};

Проблема в том, что на результаты, возвращенные функциями