Common Problems

Несмотря на значительные преимущества, многопоточность имеет свои ограничения. При увеличении количества потоков возникает ряд факторов, препятствующих линейному росту производительности.

Два ключевых теоретических закона — Закон Амдала и Закон универсальной масштабируемости - объясняют, почему увеличение числа потоков не всегда приводит к пропорциональному ускорению выполнения программы.

Закон Амдала описывает пределы ускорения программы при увеличении числа потоков. Даже если большая часть программы может быть распараллелена, всегда останется часть, которая выполняется последовательно. Эта последовательная часть становится “узким горлышком” и ограничивает прирост производительности. В результате, по мере увеличения числа потоков, прирост производительности начинает уменьшаться.

Закон универсальной масштабируемости (Закон Буттлера) идёт дальше, объясняя, почему производительность может даже снижаться при увеличении числа потоков. Он учитывает два ключевых фактора:

  • Координационные затраты: С ростом числа потоков возрастает необходимость их синхронизации и координации. Это требует дополнительных ресурсов и времени.
  • Конкуренция за ресурсы: Потоки начинают конкурировать за ограниченные ресурсы системы, такие как процессорное время, память или доступ к файлам.

Закон Буттлера показывает, что при чрезмерном увеличении числа потоков производительность не только перестаёт расти, но может начать снижаться. Это связано с тем, что затраты на управление потоками начинают превышать выгоды от их параллельного выполнения.

Общие ресурсы и синхронизация потоков

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

Состояние гонки

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

Проблемы согласованности данных

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

Дедлоки и взаимные блокировки (Deadlocks)

Дедлок - это ситуация, при которой два или более потока блокируют друг друга, ожидая освобождения ресурсов, которые удерживаются другим потоком. Чтобы понять это, нужно рассмотреть, что такое «блокировки» и «ресурсы».

Блокировка - это способ «захвата» ресурса, чтобы предотвратить доступ других потоков к этому ресурсу до завершения текущей операции.

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

Простой пример дедлока:

  • Поток A захватывает ресурс 1 (например, файл) и пытается получить доступ к ресурсу 2 (например, переменной), но этот ресурс уже захвачен потоком B.
  • Поток B захватывает ресурс 2 и пытается получить доступ к ресурсу 1, который удерживается потоком A.

В результате оба потока зависают — каждый ждёт освобождения ресурса, который удерживает другой поток. Это называется дедлоком, и программа перестаёт выполнять свои задачи, так как ни один из потоков не может продолжить работу.