Модуль `concurrent` в Python

Модуль `concurrent` в Python

В этой статье мы объясним модуль concurrent в Python.

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

YouTube Video

Модуль concurrent в Python

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

Различие между конкурентностью и параллелизмом

  • Конкурентность означает организацию процесса так, чтобы несколько задач выполнялись путем переключения между ними маленькими порциями работы. Даже если задачи фактически не выполняются одновременно, использование "времени ожидания" позволяет сделать весь процесс более эффективным.

  • Параллелизм — это механизм, который физически выполняет несколько задач одновременно. Используя несколько ядер процессора, обработка выполняется одновременно.

Оба метода ускоряют выполнение задач, но конкурентность — это вопрос проектирования ('как разделить выполнение'), а параллелизм — вопрос реализации ('как оно работает'), что делает их принципиально разными.

Что такое модуль concurrent?

concurrent — это стандартная библиотека Python, предоставляющая высокоуровневый API для безопасной и простой работы с конкурентностью и параллелизмом. Она разработана так, что вы можете сосредоточиться на 'выполнении задач', не заботясь о низкоуровневых операциях, таких как создание и управление потоками или процессами.

Назначение ThreadPoolExecutor и ProcessPoolExecutor

Модуль concurrent предоставляет два основных варианта в зависимости от типа задачи.

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

  • ProcessPoolExecutor Эта реализация предназначена для параллельной обработки и подходит для задач, нагружающих CPU. Она использует несколько процессов для полноценного задействования всех доступных ядер процессора.

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

Основы ThreadPoolExecutor (для задач ввода-вывода)

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

 1from concurrent.futures import ThreadPoolExecutor
 2import time
 3
 4def fetch_data(n):
 5    # Simulate an I/O-bound task
 6    time.sleep(1)
 7    return f"data-{n}"
 8
 9with ThreadPoolExecutor(max_workers=3) as executor:
10    futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12    for future in futures:
13        print(future.result())
  • В этом примере несколько задач ввода-вывода, ожидающих одну секунду, выполняются конкурентно. С помощью submit вызовы функций регистрируются как асинхронные задачи, а с помощью result() вы можете дождаться завершения и получить результат, что позволяет реализовать конкурентную обработку с эффективным использованием времени ожидания простым способом.

Простая конкурентность с помощью map

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

 1from concurrent.futures import ThreadPoolExecutor
 2import time
 3
 4def fetch_data(n):
 5    # Simulate an I/O-bound task
 6    time.sleep(1)
 7    return f"data-{n}"
 8
 9with ThreadPoolExecutor(max_workers=3) as executor:
10    results = executor.map(fetch_data, range(5))
11
12    for result in results:
13        print(result)
  • В этом примере несколько задач ввода-вывода выполняются конкурентно с помощью ThreadPoolExecutor.map. Так как map возвращает результаты в том же порядке, что и входные данные, вы можете писать код, похожий на последовательный, и конкурентное выполнение возможно без явного управления асинхронностью, что является большим плюсом.

Основы ProcessPoolExecutor (для задач, нагружающих CPU)

Для сложных вычислений, полностью загружающих процессор, лучше использовать процессы, а не потоки. Это позволяет обойти ограничение глобальной блокировки интерпретатора (GIL).

 1from concurrent.futures import ProcessPoolExecutor
 2
 3def heavy_calculation(n):
 4    # Simulate a CPU-bound task
 5    total = 0
 6    for i in range(10_000_000):
 7        total += i * n
 8    return total
 9
10if __name__ == "__main__":
11    with ProcessPoolExecutor(max_workers=4) as executor:
12        results = executor.map(heavy_calculation, range(4))
13
14        for result in results:
15            print(result)

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

Обработка по мере завершения с помощью as_completed

as_completed полезен, когда вам нужно обрабатывать результаты в порядке их завершения.

 1from concurrent.futures import ThreadPoolExecutor, as_completed
 2import time
 3
 4def fetch_data(n):
 5    # Simulate an I/O-bound task
 6    time.sleep(1)
 7    return f"data-{n}"
 8
 9with ThreadPoolExecutor(max_workers=3) as executor:
10    futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12    for future in as_completed(futures):
13        print(future.result())
  • В этом примере несколько асинхронных задач выполняются одновременно, и результаты обрабатываются в том порядке, в котором они завершаются. Использование as_completed позволяет быстро обрабатывать результаты независимо от порядка задач, что удобно для отображения прогресса или случаев, где требуется последовательная обработка.

Обработка исключений

В concurrent исключения возбуждаются при вызове result().

 1from concurrent.futures import ThreadPoolExecutor
 2
 3def risky_task(n):
 4    # Simulate a task that may fail for a specific input
 5    if n == 3:
 6        raise ValueError("Something went wrong")
 7    return n * 2
 8
 9with ThreadPoolExecutor() as executor:
10    futures = [executor.submit(risky_task, i) for i in range(5)]
11
12    for future in futures:
13        try:
14            print(future.result())
15        except Exception as e:
16            print("Error:", e)
  • Этот пример показывает, что даже если некоторые задачи вызывают исключения, остальные продолжают выполнение и вы можете обрабатывать исключения индивидуально при получении результата. Используя Future из concurrent, важно, что успехи и неудачи в асинхронной обработке можно безопасно управлять.

Рекомендации по выбору между потоками и процессами

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

На практике следующие критерии помогут вам сделать выбор.

  • Для процессов с большим количеством ожиданий ввода-вывода, таких как обмен данными или операции с файлами, используйте ThreadPoolExecutor.
  • Для ресурсоёмких вычислительных задач используйте ProcessPoolExecutor.
  • Если есть много простых задач, использование map позволит сделать код более лаконичным.
  • Если важно строгий контроль порядка выполнения или обработка исключений, комбинируйте submit с as_completed.

Преимущества использования concurrent

Используя модуль concurrent, вы можете обрабатывать асинхронные задачи безопасно и интуитивно понятно.

Основные преимущества таковы:.

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

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

Резюме

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

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video