Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → GIL и конкурентность в Python

GIL и конкурентность в Python: что ограничивает параллелизм

Глобальная блокировка интерпретатора: влияние на потоки, отличие операций ввода-вывода от вычислений, способы обхода и проверка фактического ускорения.

Прямой ответ

Глобальная блокировка интерпретатора (GIL) позволяет одному потоку выполнять байткод в данный момент, поэтому вычислительные задачи не ускоряются добавлением потоков.

Тип задачи Влияние GIL
Ожидание ввода-вывода Блокировка освобождается, потоки выполняются параллельно
Вычисления на байткоде Один поток в момент времени, ускорения нет
Вызов внешней библиотеки Блокировка может освобождаться на время вызова
import threading, time

def io_task():
    time.sleep(1)          # блокировка освобождается

def cpu_task():
    sum(i * i for i in range(10**7))  # блокировка удерживается

Задача с ожиданием ввода-вывода освобождает блокировку на время ожидания, поэтому несколько потоков ожидают одновременно. О практике работы с потоками — в статье про threading.

Вычислительная задача удерживает блокировку, поэтому добавление потоков не сокращает общее время выполнения.

Освобождение блокировки и способы обхода ограничения

Блокировка освобождается в определённых ситуациях, поэтому поведение зависит от характера работы.

Ожидание ввода-вывода. Поток, ожидающий ответа сети либо диска, освобождает блокировку, поэтому другой поток продолжает выполнение.

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

Переключение потоков. Блокировка периодически освобождается для переключения между потоками, поэтому один поток не занимает интерпретатор постоянно.

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

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

Три признака того, что задача не ускоряется потоками.

Вычисления выполняются на байткоде. Блокировка удерживается, поэтому потоки выполняются последовательно.

Отсутствие операций ввода-вывода. Освобождение блокировки не происходит, поэтому параллелизм не достигается.

Внешняя библиотека не освобождает блокировку. Вызов выполняется с удержанием блокировки, поэтому другие потоки ожидают.

О выборе между потоками и процессами — в статье про выбор модели конкурентности.

Переключение потоков и интервал удержания блокировки

Блокировка освобождается периодически, поэтому один поток не занимает интерпретатор постоянно.

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

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

Вытеснение при блокирующем вызове. Поток, выполняющий блокирующий вызов, освобождает блокировку сразу, поэтому переключение не ждёт окончания интервала.

Практическое следствие: интервал переключения влияет на распределение времени между потоками, поэтому вычислительный поток не занимает интерпретатор постоянно.

Практика: работа с ограничением блокировки

1. Определите характер задачи. Ожидание ввода-вывода либо вычисления определяют, достигается ли ускорение потоками.

2. Используйте потоки для задач с ожиданием. Блокировка освобождается на время ожидания, поэтому потоки выполняются параллельно.

3. Используйте процессы для вычислительных задач. Каждый процесс имеет собственный интерпретатор, поэтому блокировка не общая.

4. Проверяйте фактическое ускорение. Добавление потоков к вычислительной задаче не сокращает время выполнения.

5. Учитывайте освобождение блокировки внешней библиотекой. Длительный вызов может выполняться параллельно, если библиотека освобождает блокировку.

6. Не добавляйте потоки без измерения. Ускорение определяется характером задачи, а не числом потоков.

7. Учитывайте накладные расходы переключения. Переключение между потоками занимает время, поэтому избыточное число потоков снижает производительность.

Рекомендации:

  1. Различайте конкурентность и параллелизм. Потоки обеспечивают конкурентность, процессы — параллелизм вычислений.
  2. Учитывайте стоимость межпроцессного обмена. Передача данных между процессами требует сериализации, поэтому объём обмена влияет на время.
  3. Проверяйте поведение внешней библиотеки. Библиотека, освобождающая блокировку, позволяет достичь параллелизма без процессов.
  4. Измеряйте время до и после изменения. Фактическое ускорение подтверждает выбор модели конкурентности.
  5. Пересматривайте выбор при изменении характера задачи. Переход от ожидания к вычислениям делает потоки непригодными.

Практический сценарий: добавление потоков не ускорило обработку

Контекст: обработка данных выполнялась в нескольких потоках, но общее время не сократилось.

  1. Диагностика. Задача состояла из вычислений над набором данных без операций ввода-вывода. Потоки выполнялись последовательно, поскольку блокировка удерживалась в течение вычислений.
  2. Причина. Вычислительная задача не освобождает блокировку, поэтому добавление потоков не даёт параллелизма. Время переключения между потоками добавлялось к общему времени.
  3. Действия. (а) Определили характер задачи как вычислительный; (б) перешли на многопроцессную модель, чтобы каждое вычисление выполнялось в отдельном процессе; (в) разделили данные на части, чтобы обмен между процессами был минимальным; (г) измерили время выполнения до и после изменения; (д) зафиксировали правило выбора модели по характеру задачи.
  4. Результат. Время обработки сократилось пропорционально числу процессов, поскольку вычисления выполняются параллельно.

Вывод: вычислительная задача не ускоряется добавлением потоков, поскольку блокировка удерживается в течение вычислений. Многопроцессная модель обеспечивает параллелизм, поскольку каждый процесс имеет собственный интерпретатор.

FAQ

Что делает GIL? Позволяет одному потоку выполнять байткод интерпретатора в данный момент, поэтому вычислительные задачи не выполняются параллельно в потоках.

Освобождается ли блокировка при ожидании ввода-вывода? Да, поток, ожидающий ответа сети либо диска, освобождает блокировку, поэтому другой поток продолжает выполнение.

Как ускорить вычислительную задачу? Многопроцессной моделью, поскольку каждый процесс имеет собственный интерпретатор и собственную блокировку.

Всегда ли внешняя библиотека освобождает блокировку? Нет, поведение зависит от реализации библиотеки. Длительный вызов может выполняться с удержанием блокировки.

Источники: Python threading, multiprocessing, PEP 703 — ссылки в шапке.