Напоследок главный поток вызывает функцию get()
обоих будущих результатов, возвращенных асинхронными вызовами, чтобы дождаться завершения задач (10), (11) и проверить получившееся состояние. Отметим, что задача
В случае исключения мы устанавливаем сигнал go
, чтобы не оказалось висячего потока, и возбуждаем исключение повторно (12). Будущие результаты, соответствующие обеим задачам (4), были объявлены последними, поэтому уничтожаются первыми, и их деструкторы ждут завершения задач, если они еще не завершились.
Хотя служебного кода многовато для тестирования двух простых вызовов, что-то в этом роде все равно необходимо, чтобы проверить именно то, что мы хотим проверить. Например, запуск потока занимает довольно много времени, поэтому если бы мы не заставили потоки ждать сигнала go
, то поток, помещающий данные, вполне мог бы завершиться еще до запуска потока, извлекающего данные, а это шло бы вразрез с целью данного теста. Благодаря использованию будущих результатов мы можем быть уверены, что оба потока запущены и блокированы в ожидании одного и того же будущего. Как только это будущее наступит, оба потока начнут работать. Привыкнув к этой структуре, вы без труда напишете и другие тесты. Продемонстрированный принцип без труда обобщается на случай, когда в каком-то тесте требуется более двух потоков.
До сих пор мы говорили о
Одна из основных причин распараллеливания кода — задействовать многоядерные процессоры для повышения производительности программы. Поэтому проверять, что производительность действительно возросла, так же важно, как при использовании других методов оптимизации.
Когда распараллеливание применяется ради повышения производительности, особый интерес представляет
В предыдущих главах было показано, что конкуренция между процессорами за доступ к структуре данных может весьма негативно сказаться на производительности. Программа, которая хорошо масштабируется, когда число процессоров мало, может повести себя никуда не годно при увеличении их числа из-за возрастания конкуренции.
Следовательно, при тестировании производительности многопоточной программы лучше замерять результаты в максимально широком спектре конфигураций, чтобы можно было составить целостное представление о масштабируемости. Как минимум, следует прогнать тесты на однопроцессорной машине и на машине с максимальным числом процессорных ядер, которое вы можете себе позволить.
В этой главе мы рассмотрели различные виды ошибок, связанных с параллелизмом, — от взаимоблокировок и активных блокировок до гонок за данными и других проблематичных состояний гонки. Были описаны различные методы поиска ошибок. Я сформулировал вопросы, над которыми следует поразмыслить, дал рекомендации по написанию тестопригодного кода и рассказал о том, как структурировать тесты для параллельных программ. И, наконец, мы затронули вопрос о некоторых служебных компонентах, которые могут оказать помощь в процессе тестирования.