Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - [24]

Шрифт
Интервал

идеально подходит для разбиения работы между потоками. Предположим, что первоначально в стеке находится два элемента, поэтому можно с уверенностью сказать, что между >empty() и >top() не будет гонки ни в одном потоке. Теперь рассмотрим возможные варианты выполнения программы.

Если стек защищен внутренним мьютексом, то в каждый момент времени лишь один поток может исполнять любую функцию-член стека, поэтому обращения к функциям-членам строго чередуются, тогда как вызовы >do_something() могут исполняться параллельно. Вот одна из возможных последовательностей выполнения:

Поток А>-                    -Поток В

>if (!s.empty())

>                            if (!s.empty())

> int const value = s.top();

>                             int const value = s.top();

>s.pop();

>do_something(value);        s.pop();

>                            do_something(value);

Как видите, если работают только эти два потока, то между двумя обращениями к >top() никто не может модифицировать стек, так что оба потока увидят одно и то же значение. Однако беда в том, что между обращениями к>pop()нет обращений к>top(). Следовательно, одно из двух хранившихся в стеке значений никто даже не прочитает, оно будет просто отброшено, тогда как другое будет обработано дважды. Это еще одно состояние гонки, и куда более коварное, чем неопределенное поведение в случае гонки между >empty() и >top(), — на первый взгляд, ничего страшного не произошло, а последствия ошибки проявятся, скорее всего, далеко от места возникновения, хотя, конечно, всё зависит от того, что именно делает функция >do_something().

Для решения проблемы необходимо более радикальное изменение интерфейса — выполнение обеих операций >top() и >pop() под защитой одного мьютекса. Том Каргилл[4] указал, что такой объединенный вызов приводит к проблемам в случае, когда копирующий конструктор объектов в стеке может возбуждать исключения. С точки зрения безопасности относительно исключений, задачу достаточно полно решил Герб Саттер[5], однако возможность возникновения гонки вносит в нее новый аспект.

Для тех, кто незнаком с историей вопроса, рассмотрим класс >stack>. Вектор — это контейнер с динамически изменяемым размером, поэтому при копировании вектора библиотека должна выделить из кучи память. Если система сильно загружена или имеются жесткие ограничения на ресурсы, то операция выделения памяти может завершиться неудачно, и тогда копирующий конструктор вектора возбудит исключение >std::bad_alloc. Вероятность такого развития событий особенно велика, если вектор содержит много элементов. Если бы функция >pop() возвращала вытолкнутое из стека значение, а не только удаляла его из стека, то мы получили бы потенциальную проблему: вытолкнутое значение возвращается вызывающей программе только после модификации стека, но в процессе копирования возвращаемых данных может возникнуть исключение. Если такое случится, то только что вытолкнутые данные будут потеряны — из стека они удалены, но никуда не скопированы! Поэтому проектировщики интерфейса >std::stack разбили операцию на две: получить элемент, находящийся на вершине (>top()), а затем удалить его из стека (>pop()). Теперь, данные, которые не удалось скопировать, остаются в стеке; если проблема связана с нехваткой памяти в куче, то, возможно, приложение сможет освободить немного памяти и попытаться выполнить операцию еще раз.

Увы, это как раз то разбиение, которого мы пытались избежать в попытке уйти от гонки! К счастью, альтернативы имеются, но они не бесплатны.

Вариант 1: передавать ссылку

Первый вариант решения — передавать функции >pop() ссылку на переменную, в которую она должна будет поместить вытолкнутое из стека значение:

>std::vector result;

>some_stack.pop(result);

Во многих случаях это приемлемо, но есть и очевидный недостаток: вызывающая программа должна до обращения к функции сконструировать объект того типа, которым конкретизирован стек, чтобы передать его в качестве аргумента. Для некоторых типов это не годится, так как конструирование дорого обходится с точки зрения времени или потребления ресурсов. Для других типов это вообще может оказаться невозможно, так как конструкторы требуют параметров, которые в данной точке программы могут быть недоступны. Наконец, требуется, чтобы хранящийся в стеке тип допускал присваивание. Это существенное ограничение, многие пользовательские типы не поддерживают присваивание, хотя могут поддерживать конструирование перемещением и даже копированием (и потому допускают возврат по значению).

Вариант 2: потребовать наличия копирующего конструктора, не возбуждающего исключений, или перемещающего конструктора

Проблема с безопасностью относительно исключений в варианте функции pop(), возвращающей значение, проявляется только тогда, когда исключение может возникать в процессе возврата значения. Во многих типах имеются копирующие конструкторы, которые не возбуждают исключений, а после поддержки в стандарте С++ ссылок на r-значения (см. приложение А, раздел А.1), появилось еще много типов, в которых перемещающий конструктор не возбуждает исключений, даже если копирующий конструктор может их возбуждать. Один из вариантов решения заключается в том, чтобы наложить на потокобезопасный стек ограничение: в нем можно хранить только типы, поддерживающие возврат по значению без возбуждения исключений.


Еще от автора Энтони Д Уильямс
Викиномика. Как массовое сотрудничество изменяет всё

Это знаменитый бестселлер, который научит вас использовать власть массового сотрудничества и покажет, как применять викиномику в вашем бизнесе. Переведенная более чем на двадцать языков и неоднократно номинированная на звание лучшей бизнес-книги, "Викиномика" стала обязательным чтением для деловых людей во всем мире. Она разъясняет, как массовое сотрудничество происходит не только на сайтах Wikipedia и YouTube, но и в традиционных компаниях, использующих технологии для того, чтобы вдохнуть новую жизнь в свои предприятия.Дон Тапскотт и Энтони Уильямс раскрывают принципы викиномики и рассказывают потрясающие истории о том, как массы людей (как за деньги, так и добровольно) создают новости, изучают геном человека, создают ремиксы любимой музыки, находят лекарства от болезней, редактируют школьные учебники, изобретают новую косметику, пишут программное обеспечение и даже строят мотоциклы.Знания, ресурсы и вычислительные способности миллиардов людей самоорганизуются и превращаются в новую значительную коллективную силу, действующую согласованно и управляемую с помощью блогов, вики, чатов, сетей равноправных партнеров и личные трансляции.


Рекомендуем почитать
Стандарты программирования на С++. 101 правило и рекомендация

Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на С++, обобщенный двумя экспертами мирового класса.Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.


Практика и проблематика моделирования бизнес-процессов

Цель книги – познакомить читателей с существующими подходами и решениями в области моделирования бизнес-архитектуры предприятия. В книге освещаются различные аспекты данной проблематики, в том числе такие вопросы как базовые подходы к моделированию и возможности современных инструментальных средств.Особое внимание уделяется специфике организации проектов по разработке моделей бизнес-архитекуры. На основе практического опыта реализации проектов по моделированию бизнес-процессов в различных предметных областях проанализированы и обобщены типичные риски, ошибки и заблуждения основных участников, даны рекомендации по их предупреждению.


Освой самостоятельно С++ за 21 день

В книге широко представлены возможности новейшей версии программного продукта Microsoft Visual C++. Подробно описаны средства и подходы программирования современных профессиональных приложений. Материалы книги дополнены многочисленными демонстрационными программами, в процессе разработки которых максимально используются возможности программных инструментов Microsoft Visual Studio. Особое внимание уделено новинкам версии 6.0 и новейшим технологиям объектно-ориентированного программирования, включая использование библиотеки MFC и шаблонов классов, а также создание связанных списков.


Применение Windows API

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Использование ListView в режиме виртуального списка

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.