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

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

> }


>public:

> X(connection_info const& connection_details_):

>  connection_details(connection_details_) {}


> void send_data(data_packet const& data)←(1)

> {

>  std::call_once(

>   connection_init_flag, &X::open_connection, this);←┐

>  connection.send_data(data);                        │

> }                                                   │

> data_packet receive_data() { ←(3)

>  std::call_once(                                    │

>   connection_init_flag, &X::open_connection, 2)    (2)

>   this);                                           ←┘

>  return connection.receive_data();

> }

>};

В этом примере инициализация производится либо при первом обращении к >send_data()(1), либо при первом обращении к >receive_data()(3). Поскольку данные инициализируются функцией-членом >open_connection(), то требуется передавать также указатель >this. Как и во всех функциях из стандартной библиотеки, которые принимают объекты, допускающие вызов, (например, конструктор >std::thread и функция >std::bind()), это делается путем передачи >std::call_once() дополнительного аргумента (2).

Следует отметить, что, как и в случае >std:mutex, объекты типа >std::once_flag нельзя ни копировать, ни перемещать, поэтому, если вы собираетесь использовать их как члены классы, то соответствующие конструкторы придется определить явно (если это необходимо).

Возможность гонки при инициализации возникает, в частности, при объявлении локальной переменной с классом памяти >static. По определению, инициализация такой переменной происходит, когда поток управления программы первый раз проходит через ее объявление. Но если функция вызывается в нескольких потоках, то появляется потенциальная возможность гонки за то, кто определит переменную первым. Во многих компиляторах, выпущенных до утверждения стандарта С++11, эта гонка действительно приводит к проблемам, потому что любой из нескольких потоков, полагая, что успел первым, может попытаться инициализировать переменную. Может также случиться, что некоторый поток попытается использовать переменную после того, как инициализация началась в другом потоке, но до того, как она закончилась. В С++11 эта проблема решена: по определению, инициализация производится ровно в одном потоке, и никакому другому потоку не разрешено продолжать выполнение, пока инициализация не завершится, поэтому потоки конкурируют лишь за право выполнить инициализацию первым, ничего более серьёзного случиться не может. Это свойство можно использовать как альтернативу функции >std::call_once, когда речь идет об инициализации единственной глобальной переменной:

>class my_class;

> my_class& get_my_class_instance() {

> static my_class instance; ←┐Гарантируется, что инициализация

> return instance;          (1) потокобезопасна

>}

Теперь несколько потоков могут вызывать функцию >get_my_class_instance()(1), не опасаясь гонки при инициализации.

Защита данных только на время инициализации — частный случай более общего сценария: доступ к редко обновляемой структуре данных. Обычно к такой структуре обращаются для чтения, когда ни о какой синхронизации можно не беспокоиться. Но иногда требуется обновить данные в ней. Нам необходим такой механизм защиты, который учитывал бы эти особенности.

3.3.2. Защита редко обновляемых структур данных

Рассмотрим таблицу, в которой хранится кэш записей DNS, необходимых для установления соответствия между доменными именами и IP-адресами. Как правило, записи DNS остаются неизменными в течение длительного времени — зачастую многих лет. Новые записи, конечно, добавляются — скажем, когда открывается новый сайт — но на протяжении всей своей жизни обычно не меняются. Периодически необходимо проверять достоверность данных в кэше, но и тогда обновление требуется, лишь если данные действительно изменились.

Но хотя обновления происходят редко, они все же случаются, и если к кэшу возможен доступ со стороны нескольких потоков, то необходимо обеспечить надлежащую защиту, чтобы ни один поток, читающий кэш, не увидел наполовину обновленной структуры данных. Если структура данных не специализирована для такого способа использования (как описано в главах 6 и 7), то поток, который хочет обновить данные, должен получить монопольный доступ к структуре на все время выполнения операции. После того как операция обновления завершится, структуру данных снова смогут одновременно читать несколько потоков.

Использование >std::mutex для защиты такой структуры данных излишне пессимистично, потому что при этом исключается даже возможность одновременного чтения, когда никакая модификация не производится. Нам необходим какой-то другой вид мьютекса. Такой мьютекс есть, и обычно его называют мьютексом чтения-записи (reader-writer mutex), потому что он допускает два режима: монопольный доступ со стороны одного «потока-писателя» и параллельный доступ со стороны нескольких «потоков-читателей».

В новой стандартной библиотеке С++ такой мьютекс не предусмотрен, хотя комитету и было подано предложение[6]. Поэтому в этом разделе мы будем пользоваться реализацией из библиотеки Boost, которая основана на отвергнутом предложении. В главе 8 вы увидите, что использование такого мьютекса — не панацея, а его производительность зависит от количества участвующих процессоров и относительного распределения нагрузки между читателями и писателями. Поэтому важно профилировать работу программу в целевой системе и убедиться, что добавочная сложность действительно дает какой-то выигрыш.


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

Это знаменитый бестселлер, который научит вас использовать власть массового сотрудничества и покажет, как применять викиномику в вашем бизнесе. Переведенная более чем на двадцать языков и неоднократно номинированная на звание лучшей бизнес-книги, "Викиномика" стала обязательным чтением для деловых людей во всем мире. Она разъясняет, как массовое сотрудничество происходит не только на сайтах 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.