Сравнение create_subprocess_shell и create_subprocess_exec в asyncio
Обе функции asyncio.create_subprocess_shell
и asyncio.create_subprocess_exec
нужны для запуска программ прямо изнутри скрипта на Python. Эти функции очень похожи и, чтобы разобраться в отличиях, рассмотрим пример небольшого скрипта. Он запускает долго исполняющуюся программу sleep 100
и ждёт её завершения:
import asyncio
async def main():
process = await asyncio.create_subprocess_shell('sleep 100')
await process.communicate()
asyncio.run(main())
Запустим скрипт, проверим как быстро он отработает:
$ python script.py
Действительно, скрипт “думает” больше минуты — ждёт окончания работы программы sleep 100
. Всё прошло как и ожидалось.
Проблемы начнутся, когда попытаемся прервать работу программы sleep 100
.
В официальной документации
указано, что create_subprocess_shell
запускает команду внутри шелла - командной оболочки sh
.
На практике это означает, что вместо одного процесса будет запущено сразу два — один для sh
и второй для sleep
.
Кол-во процессов важно учитывать из-за особенностей работы sh
. Дело в том, что остановка командной оболочки не приводит к остановке запущенной ей команды. Если вы попытаетесь остановить sleep
через объект process
, то остановите только sh
, но не sleep
.
Проверим это. Добавим в скрипт вызов process.terminate()
:
import asyncio
async def main():
process = await asyncio.create_subprocess_shell('sleep 100')
# wait till sleep will be actually launched by shell
await asyncio.sleep(1)
process.terminate()
await process.communicate() # wait till termination is over
asyncio.run(main())
Запустим в консоли скрипт, он отработает буквально за пару секунд:
$ python script.py
Сразу после проверим, остановилась ли программа sleep
:
$ ps a| grep sleep
77569 pts/1 S+ 0:00 sleep 100
77721 pts/3 S+ 0:00 grep --color=auto sleep
Оказывается, sleep
всё еще работает. Вызов метода process.terminate()
остановил командную оболочку sh
, но не запущенную внутри программу sleep
.
Преимущество create_subprocess_exec
Проблем с остановкой процесса можно избежать, если вместо функции asyncio.create_subprocess_shell
использовать аналогичную ей asyncio.create_subprocess_exec
. Разница в том, что функция create_subprocess_shell
запускает только командную оболочку sh
, а asyncio.create_subprocess_exec
— сразу программу sleep 100
.
Тот же пример с запуском и остановкой sleep
, но переписан уже с использованием create_subprocess_exec
:
import asyncio
async def main():
process = await asyncio.create_subprocess_exec('sleep', '100')
# wait till sleep will be actually launched
await asyncio.sleep(1)
process.terminate()
await process.communicate() # wait till termination is over
asyncio.run(main())
В отличие от прежней версии вызов process.terminate()
остановит саму команду sleep
, а не только командную оболочку sh
.
Особенности Mac OS
На Mac OS функция create_subprocess_shell
ведёт себя иначе, чем в Linux. Командная оболочка не создаёт отдельный процесс для sleep
. С одной стороны это удобно, код становится проще и приятнее. С другой стороны, если скрипт вам предстоит запускать на Linux-сервере, то тестировать его тоже следует на Linux.
Вариант первый. Запускайте скрипт внутри Docker образа.
Вариант второй. Отдельно протестируйте скрипт на Linux сервере.