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

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

К счастью, в стандартной библиотеке есть на этот случай лекарство в виде функции >std::lock, которая умеет захватывать сразу два и более мьютексов без риска получить взаимоблокировку. В листинге 3.6 показано, как воспользоваться ей для реализации простой операции обмена.


Листинг 3.6. Применение >std::lock и >std::lock_guard для реализации операции обмена

>class some_big_object;

>void swap(some_big_object& lhs, some_big_object& rhs);


>class X {

>private:

> some_big_object some_detail;

> std::mutex m;

>public:

> X(some_big_object const& sd) : some_detail(sd) {}

> friend void swap(X& lhs, X& rhs) {

>  if (&lhs == &rhs)

>   return;

>  std::lock(lhs.m, rhs.m); ←(1)

>  std::lock_guard lock_a(lhs.m, std::adopt_lock);←(2)

>  std::lock_guard lock_b(rhs.m, std::adopt_lock);←(3)

>  swap(lhs.some_detail,rhs.some_detail);

> }

>};

Сначала проверяется, что в аргументах переданы разные экземпляры, постольку попытка захватить >std::mutex, когда он уже захвачен, приводит к неопределенному поведению. (Класс мьютекса, допускающего несколько захватов в одном потоке, называется >std::recursive_mutex. Подробности см. в разделе 3.3.3.) Затем мы вызываем >std::lock()(1), чтобы захватить оба мьютекса, и конструируем два экземпляра >std::lock_guard(2), (3) — по одному для каждого мьютекса. Помимо самого мьютекса, конструктору передается параметр >std::adopt_lock, сообщающий объектам >std::lock_guard, что мьютексы уже захвачены, и им нужно лишь принять владение существующей блокировкой, а не пытаться еще раз захватить мьютекс в конструкторе.

Это гарантирует корректное освобождение мьютексов при выходе из функции даже в случае, когда защищаемая операция возбуждает исключение, а также возврат результата сравнения в случае нормального завершения. Стоит также отметить, что попытка захвата любого мьютекса >lhs.m или >rhs.m внутри >std::lock может привести к исключению; в этом случае исключение распространяется на уровень функции, вызвавшей >std::lock. Если >std::lock успешно захватила первый мьютекс, но при попытке захватить второй возникло исключение, то первый мьютекс автоматически освобождается; >std::lock обеспечивает семантику «все или ничего» в части захвата переданных мьютексов.

Хотя >std::lock помогает избежать взаимоблокировки в случаях, когда нужно захватить сразу два или более мьютексов, она не в силах помочь, если мьютексы захватываются порознь, — в таком случае остается полагаться только на дисциплину программирования. Это нелегко, взаимоблокировки — одна из самых неприятных проблем в многопоточной программе, часто они возникают непредсказуемо, хотя в большинстве случаев все работает нормально. Однако все же есть несколько относительно простых правил, помогающих писать свободный от взаимоблокировок код.

3.2.5. Дополнительные рекомендации, как избежать взаимоблокировок

Взаимоблокировка может возникать не только при захвате мьютексов, хотя это и наиболее распространенная причина. Нарваться на взаимоблокировку можно и тогда, когда есть два потока и никаких мьютексов; достаточно, чтобы каждый поток вызвал функцию >jоin() объекта >std::thread, связанного с другим потоком. В этом случае ни один поток не сможет продолжить выполнение, потому что будет ждать завершения другого потока, — точно так же, как дети, ссорящиеся по поводу игрушек. Такой простой цикл может возникнуть всюду, где один поток ждет завершения другого для продолжения работы, а этот другой поток одновременно ждет завершения первого. Причем потоков необязательно должно быть два — цикл, в котором участвуют три и более потоков, также приведёт к взаимоблокировке. Общая рекомендация во всех случаях одна: не ждите завершения другого потока, если есть малейшая возможность, что он будет дожидаться вас. Ниже приведены конкретные советы, как выявить и устранить такую возможность.

Избегайте вложенных блокировок

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

Старайтесь не вызывать пользовательский код, когда удерживаете мьютекс

По существу, это простое развитие предыдущей рекомендации. Поскольку код написан пользователем, вы не можете знать, что он делает. А делать он может все, что угодно, в том числе захватывать мьютекс. Если вы вызываете пользовательский код, не освободив предварительно мьютекс, а этот код захватывает какой-то мьютекс, то оказывается нарушена рекомендация избегать вложенных блокировок, и может возникнуть взаимоблокировка. Иногда избежать этого невозможно: если вы пишете обобщенный код, например класс стека из раздела 3.2.3, то каждая операция над типом или типами параметров — не что иное, как пользовательский код. В таком случае прислушайтесь к следующему совету.


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

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


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.