Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Применяя тестирование грубой силой, важно всеми силами избегать условий, способных породить ложную уверенность. Для этого необходимо тщательно планировать тесты, уделяя внимание не только тому, какие участки кода подвергать тестированию, но также проектированию стенда (test harness) и выбору тестовой среды. Вы должны постараться протестировать как можно больше путей выполнения кода и взаимодействий между потоками. При этом еще необходимо знать, какие именно варианты протестированы, а какие остались не протестированными .
Хотя метод грубой силы дает некоторую уверенность в правильности кода, гарантировать обнаружение всех ошибок он не может. Однако существует методика, которая гарантированно находит проблемы, если только у вас есть время применить ее к своему коду и подходящее программное обеспечение. Я называю ее комбинаторным имитационным тестированием .
Название не вполне внятное, поэтому объясню, что я имею в виду. Идея в том, чтобы прогонять код под управлением специальной программы, которая имитирует реальную среду. Вам, наверное, доводилось слышать о программах, которые запускают несколько виртуальных машин на одном физическом компьютере, причём характеристики каждой виртуальной машины и оборудования эмулируются программным супервизором. Здесь всё похоже, только вместо эмулирования системы имитационное ПО записывает последовательности операций доступа к данным, захвата блокировок и атомарных операций в каждом потоке. Затем она применяет правила модели памяти, определенные в С++, чтобы повторить прогон при любой допустимой комбинации операций и таким образом выявить гонки и взаимоблокировки.
Хотя такое исчерпывающее тестирование всех комбинаций гарантированно обнаружит все проблемы, на поиск которых система рассчитана, но для любой программы, кроме самых тривиальных, оно займет чудовищно много времени, потому что количество комбинаций экспоненциально увеличивается с ростом числа потоков и операций, выполняемых в каждом потоке. Лучше применять эту методику к детальным тестам отдельных частей кода, а не ко всему приложению. Очевидный недостаток заключается том, что необходимо специальное имитационное ПО, способное обработать встречающиеся в вашей программе операции.
Таким образом, мы располагаем методикой, подразумевающей многократный прогон тестов при обычных условиях, которая, однако, может пропускать некоторые ошибки, и методикой, предполагающий запуск в специально созданных условиях, которая найдет ошибки с гораздо большей вероятностью. А есть ли еще какие-нибудь варианты?
Третий способ — воспользоваться библиотекой, которая сама обнаруживает проблемы, возникающие при прогоне тестов.
Этот вариант не обеспечивает исчерпывающего покрытия, которое дает комбинаторное имитационное тестирование, но все же позволяет выявить многие проблемы за счет специальных реализаций таких библиотечных примитивов синхронизации, как мьютексы, блокировки и условные переменные. Например, часто требуется, чтобы любой доступ к некоторым разделяемым данным, производился, когда захвачен определенный мьютекс. Если бы была возможность проверить, какие мьютексы захвачены в момент доступа к этим данным, то можно было бы сообщить об ошибке в случае, когда нужный мьютекс не захвачен. Пометив разделяемые данные определенным образом, мы смогли бы сообщить библиотеке, что проверять.
Такая реализация библиотеки могла бы также записывать последовательность захватов в случае, когда некоторый поток одновременно удерживает более одного мьютекса. Если другой поток попытается захватить те же мьютексы в другом порядке, то будет зарегистрирована потенциальная взаимоблокировка, даже если при реальном прогоне теста она не возникала.
Для тестирования многопоточного кода могла бы быть полезна и специальная библиотека другого рода, в которой реализации таких примитивов, как мьютексы и условные переменные, позволяют автору теста управлять тем, какой поток захватит блокировку, если ее ожидают несколько потоков, или какой из потоков, ожидающих условную переменную, будет разбужен вызовом notify_one()
. Это дало бы возможность настраивать конкретные сценарии и проверять, что код работает в соответствии с ожиданиями.
Некоторые из описанных средств тестирования следовало бы включать в стандартную библиотеку С++, а другие можно было бы реализовать на основе стандартной библиотеке как часть тестового стенда.
Обсудив различные способы исполнения тестового кода, посмотрим, как можно структурировать код для достижения желаемого порядка планирования потоков.
10.2.5. Структурирование многопоточного тестового кода
В разделе 10.2.2 я говорил о том, что нужно придумать, как обеспечить надлежащий порядок планирования для циклов «while» в тестах. Сейчас самое время поговорить о возникающих здесь вопросах.
Основная проблема — организовать набор потоков таким образом, чтобы каждый исполнял выбранный фрагмент кода в указанный вами момент времени. В простейшем случае потоков всего два, но решение легко обобщается и на большее число. На первом этапе нужно определиться, как устроен каждый тест:
• код общей настройки, исполняемый в самом начале;
• потоковый код настройки, исполняемый в каждом потоке;
• содержательный код, исполняемый в параллельно работающих потоках;
• код, исполняемый по завершении параллельного исполнения; может включать утверждения о состоянии программы.
Для определённости рассмотрим пример из списка в разделе 10.2.2: один поток вызывает push()
для пустой очереди, а второй в это время вызывает pop()
.
Код общей настройки очевиден: надо создать очередь. В потоке, исполняющем pop()
, нет потокового кода настройки. Потоковый код настройки для потока, исполняющего push()
, зависит от интерфейса очереди и типа сохраняемого в ней объекта. Если конструировать сохраняемый объект дорого или память для него должна выделяться из кучи, то лучше сделать это в потоковом коде настройки, чтобы не оказывать влияния на сам тест. С другой стороны, если в очереди хранятся всего лишь значения типа int
, то мы ничего не выиграем от их конструирования в коде настройки. Собственно тестируемый код тоже прост — вызвать push()
в одном потоке и pop()
в другом. А вот как быть с кодом, «исполняемым по завершении»?
В данном случае всё зависит от того, что должна делать функция pop()
. Если предполагается, что она блокирует поток до появления данных в очереди, то, очевидно, мы ожидаем, что будут возвращены данные, переданные функции push()
, и что очередь в итоге окажется пустой. Если же pop()
не блокирует поток и может вернуть управление, даже когда очередь пуста, то требуется проверить два возможных исхода: либо pop()
вернула данные, переданные push()
, и очередь пуста, либо pop()
известила об отсутствии данных и в очереди есть один элемент. Истинно должно быть ровно одно утверждение; чего мы точно не хотим, так это ситуации, когда pop()
говорит «нет данных», но очередь пуста, или когда pop()
вернула значение, а очередь все равно не пуста. Для упрощения теста предположим, что функция pop()
блокирующая. Тогда в завершающем коде должно быть утверждение вида «извлеченное значение совпадает с помещённым и очередь пуста».
Интервал:
Закладка: