September 7, 2023

Процессы, потоки и асинхронность в Python

Fooocus V2

Процесс

Допустим у нас есть программа Notepad. Когда мы запускаем Notepad, операционная система создает новый процесс notepad.exe и выделяет для него память. В этом процессе и выполняется вся программа Notepad.

В Python работает аналогично. Когда мы запускаем скрипт:

Copy codepython my_script.py

Создается отдельный процесс python.exe, выделяется память, и в нем начинает выполняться код скрипта my_script.py.
Пример с multiprocessing:

import threading

def print_doc(doc_name):
    print(f"Printing {doc_name}")

if __name__ == "__main__":
    thread1 = threading.Thread(target=print_doc, args=("doc1.txt",))
    thread2 = threading.Thread(target=print_doc, args=("doc2.txt",))

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()

В этом примере мы импортируем модуль threading и определяем функцию print_doc(), которая представляет собой задачу, выполняемую в потоке. Затем мы создаем экземпляры класса Thread, указывая функцию print_doc в качестве цели, и запускаем потоки с помощью метода start(). Метод join() используется для ожидания завершения потоков.

Поток

Представим, что в Notepad мы можем одновременно открыть два документа - doc1.txt и doc2.txt. И печатать в них одновременно. Это будут два потока внутри одного процесса Notepad.

В Python мы можем создать несколько потоков в одном процессе:

pythonCopy codeimport threading

def print_doc(doc_name):
  print(f"Printing {doc_name}")
thread1 = threading.Thread(target=print_doc, args=("doc1.txt",))
thread2 = threading.Thread(target=print_doc, args=("doc2.txt",))

thread1.start() # запуск потока
thread2.start()

Здесь в одном процессе Python запущено 2 потока, которые выполняются параллельно.

Асинхронность

Представим, что в Notepad мы можем начать печатать doc1.txt и не ждать окончания, а сразу же начать печатать doc2.txt. Это похоже на асинхронность в Python с async/await.

Пример асинхронного кода:

pythonCopy codeimport asyncio

async def print_doc(doc_name):
  print(f"Start printing {doc_name}")
  await asyncio.sleep(1) # имитируем задержку
  print(f"Done printing {doc_name}")

asyncio.run(print_doc("doc1.txt"))
asyncio.run(print_doc("doc2.txt"))

Таким образом мы запускаем асинхронные задачи параллельно.

Django приложение

Классическое Django приложение работает в одном процессе и обрабатывает каждый запрос последовательно.

Чтобы сделать его асинхронным, нужно:

  • Использовать async во views
  • Настроить ASGI сервер, например Daphne
  • Воспользоваться асинхронными библиотеками типа aiohttp

Это позволит ускорить обработку запросов за счет асинхронности.

А для масштабирования можно запустить несколько процессов через uWSGI/Gunicorn.