Можно ли работать в одном потоке в Python?

«Можно ли работать в одном потоке в Python?» — вопрос из категории Скриптинг и автоматизация, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, Python-скрипт по умолчанию выполняется в одном главном потоке. Однако для задач автоматизации в DevOps часто требуется параллельное выполнение операций (например, одновременная проверка множества серверов или параллельная загрузка файлов).

1. Многопоточность (threading): Потоки в Python подходят для задач, связанных с вводом-выводом (I/O-bound), где время тратится на ожидание сети, диска и т.д. Несмотря на Global Interpreter Lock (GIL), потоки могут работать параллельно в такие моменты ожидания.

Пример: параллельные HTTP-запросы к нескольким эндпоинтам для мониторинга:

import threading
import requests
from concurrent.futures import ThreadPoolExecutor

def check_url(url):
    try:
        resp = requests.get(url, timeout=5)
        return f"{url}: {resp.status_code}"
    except Exception as e:
        return f"{url}: ERROR - {e}"

urls = ["http://api1.example.com/health", "http://api2.example.com/health"]

# Использование ThreadPoolExecutor - лучшая практика
with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(check_url, urls))

for result in results:
    print(result)

2. Мультипроцессинг (multiprocessing): Для CPU-интенсивных задач (например, шифрование, сжатие данных) нужно использовать процессы, так как они обходят ограничение GIL, используя отдельные интерпретаторы Python.

Пример: параллельная обработка лог-файлов:

from multiprocessing import Pool
import gzip

def process_log_file(filename):
    with gzip.open(filename, 'rt') as f:
        return sum(1 for line in f if 'ERROR' in line)

file_list = ['app1.log.gz', 'app2.log.gz']
with Pool(processes=2) as pool:
    error_counts = pool.map(process_log_file, file_list)

3. Асинхронное программирование (asyncio): Идеально для высоконагруженных сетевых задач (например, свой мониторинг или оркестратор).

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in url_list]
        results = await asyncio.gather(*tasks)

Выбор подхода зависит от задачи: threading для множества I/O-операций, multiprocessing для тяжелых вычислений, asyncio для максимальной эффективности в сетевых операциях.