Общие шаблоны для неправильно работающих многопоточных приложений

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

Конфликт блокировок и сериализованное выполнение

Lock contention resulting in serialized execution

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

Как показано на следующем рисунке, визуализатор параллелизма может также обнаруживать подобные признаки в представлении использования ЦП, когда, несмотря на наличие нескольких потоков, приложение использует только одно логическое ядро.

Дополнительные сведения см. в разделе "Начало работы с проблемой" статьи MSDN Magazine Thread Performance — Resource Contention Concurrency Profiling in Visual Studio.

Lock Contention

Неравномерное распределение рабочей нагрузки

Screenshot of a workload graph for parallel threads in the Concurrency Visualizer. The threads end at different times showing a stair-step pattern.

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

Как показано на следующем рисунке, эти признаки могут проявляться в визуализаторе параллелизма в представлении использования ЦП в виде поэтапного снижения уровня использования центрального процессора.

Screenshot of the CPU Utilization View in the Concurrency Visualizer showing a stair-step pattern at the end of the CPU Utilization graph.

Превышение лимита подписки

Screenshot of a workload graph for all active threads in the Concurrency Visualizer. A legend shows the amount of time spent in Execution and Preemption.

В случае превышения лимита подписки число активных потоков процесса превышает число доступных ядер системы. На предыдущем рисунке показаны результаты превышения лимита подписки с явно выраженным наличием приоритетного прерывания во всех активных потоках. Кроме того, на легенде показано, что на приоритетное прерывание затрачена значительная доля времени (84 процента в данном примере). Это может свидетельствовать о том, что количество параллельных потоков, запрашиваемых процессом в системе, превышает число логических ядер. Однако это может также свидетельствовать о том, что ресурсы, которые предположительно должны были быть доступными для данного процесса, используются в системе другими процессами.

При анализе данной проблемы нужно учитывать следующие особенности.

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

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

Неэффективный ввод-вывод

Inefficient I/O

Избыточное или недостаточное использование ввода-вывода обычно приводит к неэффективности работы приложений. Рассмотрим предыдущий рисунок. В профиле видимой временной шкалы указано, что 44 процента видимого времени выполнения приложений потрачено на ввод-вывод. На временной шкале указано много операций ввода-вывода, что свидетельствует о частом блокировании ввода-вывода в профилируемом приложении. Чтобы просмотреть подробные сведения о характере операций ввода-вывода и блокировании программы, увеличьте проблемные области, проанализируйте профиль видимой временной шкалы, а затем щелкните определенный блок ввода-вывода, чтобы просмотреть текущие стеки вызова.

Колонны блокировок

Lock convoys

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

Представление "Потоки"