... о работе с несколькими задачами одновременно.
... о выполнении нескольких вычислений одновременно.
Время |
Вычислитель #1 |
---|---|
t + 0 |
Задача А |
t + 1 |
Задача Б |
t + 2 |
Задача А |
t + 3 |
Задача А |
t + 4 |
Задача В |
Время |
Вычислитель #1 |
Вычислитель #2 |
---|---|---|
t + 0 |
Задача А |
Задача А |
t + 1 |
Задача Б |
– |
t + 2 |
Задача В |
– |
t + 3 |
Задача Г |
Задача Г |
t + 4 |
Задача Д |
– |
Время |
Вычислитель #1 |
Вычислитель #2 |
---|---|---|
t + 0 |
Задача А |
Задача Б |
t + 1 |
Задача В |
Задача Б |
t + 2 |
Задача A |
Задача В |
t + 3 |
Задача Б |
Задача А |
t + 4 |
Задача Г |
– |
from threading import Thread def sqr(x): return x * x t = Thread(target=sqr, args=(42, )) t.start() t.join()
from concurrent.futures import ThreadPoolExecutor def sqr(x): return x * x with ThreadPoolExecutor(max_workers=3) as executor: result = list(executor.map(sqr, [1, 2, 3, 4, 5]))
from concurrent.futures import ThreadPoolExecutor def sqr(x): return x * x with ThreadPoolExecutor(max_workers=3) as executor: result = list(executor.map(sqr, [1, 2, 3, 4, 5]))
import os os.fork()
from multiprocessing import Process def sqr(x): return x * x p = Process(target=sqr, args=(42, )) p.start() p.join()
from concurrent.futures import ProcessPoolExecutor def sqr(x): return x * x with ProcessPoolExecutor(max_workers=3) as executor: result = list(executor.map(sqr, [1, 2, 3, 4, 5]))
Характеристика |
Потоки |
Процессы |
---|---|---|
Ограничение GIL |
да |
нет |
Время создания |
меньше |
больше [1] |
Потребление памяти |
меньше |
больше [1] |
Время коммуникации |
меньше |
дольше |
На большинстве *nix платформах доступна оптимизация copy-on-write, за счет чего создание процессов может происходить немногим медленнее потоков, а потребление памяти - немногим больше потоков. Тем не менее, в случае CPython потребление памяти будет расти достаточно быстро.
import eventlet; eventlet.monkey_patch() from threading import Thread def sqr(x): return x * x Thread(target=sqr, args=(42, )).start()
Зеленые потоки |
Потоки |
---|---|
Подходят только для I/O приложений. |
Подходят для любого рода приложений. |
Экономят системные ресурсы. |
Хотя и не будут исполняться параллельно. |
import asyncio async def sqr(x): return await send_sqr(x) loop = asyncio.get_event_loop() result = loop.run_until_complete(sqr(42))
Зеленые потоки |
asyncio |
---|---|
Совместимы с обычным многопоточным кодом. |
Требует использование специального синтаксиса, несовместимого с классическим кодом. |
Благодаря костылям, работают со многими библиотеками и драйверами к БД. |
Требует специальных версий библиотек и драйверов к БД. |
Существует мнение, что асинхронное программирование работает быстрее для I/O приложений за счет отсутствия необходимости переходить в kernel space для переключения между потоками.
from socket import * from threading import Thread def echo_server(conn): while True: data = conn.recv(100) if not data: break conn.send(data) s = socket(AF_INET, SOCK_STREAM) s.bind(('127.0.0.1', 5000)) s.listen(10000) while True: conn, addr = s.accept() Thread(target=echo_server, args=(conn, )).start()
import asyncio async def echo_server(reader, writer): while True: data = await reader.read(100) if not data: break writer.write(data) loop = asyncio.get_event_loop() coro = asyncio.start_server( echo_server, '127.0.0.1', 5000, loop=loop) server = loop.run_until_complete(coro) loop.run_forever()
Эхо-Сервер |
на Потоках |
на Корутинах |
---|---|---|
100 клиентов |
~ 63000 запросов/с |
~ 25000 запросов/с |
1000 клиентов |
~ 56000 запросов/с |
~ 19000 запросов/с |
5000 клиентов |
~ 2700 запросов/с [2] |
~ 19000 запросов/с |
Сервер не дошел до обслуживания 5000 клиентов. Исследование показало, что при большом количестве потоков на многоядерной системе происходит «Война за GIL». Времени на главный поток почти не выделяется, поэтому установить соединение с новыми клиентами не удается.
Тем не менее, ограничив исполнение сервера одним CPU ядром, можно получить результат около 58500 запросов/с.
(на 100 клиентах)
Эхо-Сервер |
CPU |
linux perf |
---|---|---|
на Потоках |
~ 292% |
|
на Корутинах |
~ 94% |
|
Python медленный :'(
Зато потоки могут параллельно исполняться :)
Эхо-Сервер |
на Потоках |
на Корутинах |
на Корутинах и процессах |
---|---|---|---|
100 клиентов |
~ 63000 запр./с |
~ 25000 запр./с |
~ 42000 запр./с |
1000 клиентов |
~ 56000 запр./с |
~ 19000 запр./с |
~ 40000 запр./с |
5000 клиентов |
~ 2700 запр./с |
~ 19000 запр./с |
~ 34000 запр./c |
Эхо-Сервер |
Кол-во клиентов |
Пропускная способность на локальном хосте |
Пропускная способность в сети |
---|---|---|---|
на Потоках |
1000 |
~ 63000 запросов/с |
~ 7200 запросов/с |
на Корутинах |
1000 |
~ 25000 запросов/с |
~ 7200 запросов/с |
(спасибо за внимание)