Pull to refresh

Comments 26

Ситуацию немного исправило добавление специальных директив async и await (как в JavaScript, что важно)

Вы ничего не перепутали?
* Python 3.5 was released on September 13, 2015
* ES8 was released on 2017

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

Python 3.7 was released on June 27, 2018
The new provisional asyncio.run() function can be used to run a coroutine from synchronous code by automatically creating and destroying the event loop. (Contributed by Yury Selivanov in bpo-32314.)
Вроде не перепутал, синтаксис async/await в JavaScript появился ещё в ES6, нашёл это — немного погуглив.

asyncio.run() выполняет корутины, я никогда не заставлю обычную функцию выполнятся асинхронно, так что по сути не решает проблему никак

P.S. Ошибка, нашёл инфу о том, что официальная поддержка async/await появилась только в ES8. Спасибо, исправляю
Объясните, пожалуйста, что Вы имеете ввиду. Я из приведённых ссылок понял, что у меня есть возможность запустить блокирующую функцию в параллельном потоке в цикле, в котором так же крутятся корутины. Но в итоге блокирующая функция конкурентно не выполняется. Или я что-то неверно понимаю?
asyncio.run() выполняет корутины, я никогда не заставлю обычную функцию выполнятся асинхронно, так что по сути не решает проблему никак

А как необходимость запускать блокирующую функцию в асинхронном окружении соотносится с вашей исходной задачей? Изначально вы поставили задачу — написать код один раз так, что бы его можно было использовать либо в асинхронном окружении, либо в синхронном. Тут совсем нет речи про то, что надо из асинхронного окружения вызывать какой-то блокирующий код. Это совсем другая задача.


В исходной задаче асинхронную версию библиотеки можно использовать в качестве основной и базовой реализации. А работу с ним из синхронного окружения либо переложить на пользователей (пускай сами явно используют asyncio.run() или loop.run_until_complete()). Или сделать какой-то декоратор, который ко всем вашим функциям и методам добавит синхронный враппер, использующий внутри себя asyncio.run(). Например такой враппер можно сохранять в свойство самой функции, что бы вызывать это как-то так:


my_async_func.sync()

PS: Стоит заметить, что asyncio.run(), вероятно, будет добавлять какой-то оверхед на создание евент-лупа и его удаление при каждом вызове ваших методов из синхронного окружения. Поэтому требуется тестирование. Как вариант можно попробовать реализовать однократное создание евент-лупа для вашей библиотеки с помощью какой-то функции для "настройки" работы библиотеки в синхронном окружении. И в дальнейшем использовать loop.run_until_complete().

Согласен, проблема, конечно, в постоянном возвращении управления циклу событий, но и моё решение этого не исправляет. Наверное, это было бы намного лаконичнее, спасибо!
Провёл бенчмарки — запускать асинхронный код синхронно через единично существующий loop намного быстрее, нежели через сгенерированные шаблоны! Ну окей, попытка не пытка, спасибо за хорошую идею!
Есть идеологически близкие утилиты sync_to_async и async_to_sync в asgiref. Как они вам?

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


Спасибо за ссылку!

А что async в питоне отменил GIL?

Автору все же не мешало бы понять как различаются параллельное и асинхронное исполнение.

Да и с работой goroutine в Golang тоже стоит разобраться, что бы не сравнивать ее с асинхронностью в питоне.

Нет, но разве GIL как-то влияет на асинхронный код?

GIL влияет на весь код исполняемый интерпретатором Python.
Он в итоге не дает работать более чем в один поток. Хотите больше чем в один — subprocess и запуск несколких отдельных рантаймов питона. Все что решает async — это просто смена концепции исполнения кода с синхронного на асинхронный вариант.
Зато с GIL не надо парится про параллельный доступ к разделяемым данным.

Goroutine — является по сути блоком данных, являющийся стеком. А исполняют их воркеры, которые рантайм Golang запускает при старте программы по числу доступных потоов процессора (если не ограничено явно). И вот в goroutine (если воркеров более одного) и есть параллельное исполнение (и асинхронное тоже). Но это не бесплатно — при параллельном исполнении нужно внимательно следить и защищать данные используемые разными goroutene-ами. Ситуацию чуть упрощает использование каналов, но они конечно же не покрывают все необходимости. Поэтому в Golang кроме каналов и атомики и мьютексы тоже используются.

К сожалению GIL не избавляет от необходимости следить за параллельным доступом к вашим данным. GIL защищает от параллельного доступа исключительно внутренности самого интерпретатора. А данные вашей программы он защитить не может, т.к. не знает где и как они используются.

Я знаю, я зачем-то смешал горутины из Go и корутины из Python, но я хотел просто обратить внимание на синтаксис. В Go код никак не делится на синхронный и асинхронный, а в Python — это уже 2 разных материка, смешивать функции и корутины становится в разы тяжелее. GIL ведь не управляет асинхронным кодом внутри, он может менеджить его снаружи потока, в котором запущен loop

P.S. Ну в общем если считать, что асинхронность в Python основана на генераторах и на возвращении управлении в цикл, то очевидно, что сравнивать их и нельзя, совсем по разному работают
Покажу небольшой пример шаблона, который предполагает возможность делать запросы в google:

К сожалению, шаблон получился длиннее и сложнее для восприятия, чем синхронная и асинхронная функция по отдельности.


Не проще ли просто продублировать код в разных стилях?


# синхронная функция в вашей библиотеке
from instalib import get_photos
# асинхронная
from instalib import async_get_photos
#  асинхронная вариант 2
from instalib_async import get_photos

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


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

Если все же вы настаиваете на библиотеке функций, которые могут вызываться как из синхронного, так и из асинхронного кода, то моей первой мыслью было бы создать для каждой экспортируемой функции фабрику, которая бы отдавала синхронную или асинхронную версию функции в зависимости от наличия запущенного Event Loop. Хотя нет, это плохая идея уже потому, что функция получится грязной (зависящей от внешних условий).


Может быть просто скрыть ваш шаблон в импортированном модуле, а пользователю отдавать функцию, которая внутри содержит фабрику, которая и будет запускать функцию асинхронно или синхронно?
Как-то так:


from instalib import get_photos

get_photos(id, 'sync')

# or
async get_photos(id, 'async')

Насколько я вижу, все эти сложности приводят к тому, что библиотеки питона для работы с сетью четко делятся на синхронные и асинхронные.


P.S. Позвольте порекомендовать вам к чтению вот эту статью: https://habr.com/ru/post/479186/

Шаблон я тоже пишу для себя — я собирался реализовать такие асинхронные и синхронные функции и пользователю выдавать уже готовые для использования — не шаблоны. За ссылку спасибо, обязательно прочту!
Прочёл! Отличная концепция, но как и было сказано в статье — для того, чтобы не было проблем — нужно гарантированное отсутствие go команд, поэтому пока что её можно просто придерживаться
go язык с дурацким синтаксисом, но там есть горутины и каналы, за что приходится его уважать, т.е. писать на нем
python язык с отличным синтаксисом… (пишу на обоих, если что)
Итого — нужен питоно-образный golang, а Сиpython надо переписать на go, чтоб на несколько ядер забегал. (на заметку людям, зачем-то совершающим подвиги создания идентичных клонов go (имею ввиду vlang и umka) ) вместо {{см.выше}} В конце-концов go это же такой новый Си. Пора, пора на нем питон забабахать! Не знаю, как остальные, а я бы конечно хотел питон с горутинами и канальями :-)

вот, написал, в качестве концепта :-)
print в golang:

package main
import "fmt"

func main() {
    bar := 789
    var c int
    c = 987

    print := fmt.Printf

    fmt.Printf("fmt.Printf ==> %d", c)
    print("\n python print ==> %d", c)
    print("\n python print bar ==> %d", bar)
} 

Это совсем 2 разных языка, боюсь, что это огромная работа — создавать что-то среднее между ними. Если интересно, можете обратить внимание на Nim. Python в принципе в параллельную работу не может из-за высокого уровня абстракции над Си, дело даже не в том, что он на Си основан, а в том, что там слишком много действий, которые требуют синхронизации при параллельном выполнении
Итого — нужен питоно-образный golang, а Сиpython надо переписать на go, чтоб на несколько ядер забегал.

В сторону Julia не пробовали смотреть?


Сиpython надо переписать на go

Я вас умоляю, для этого больше подойдет Rust.

Послушайте, мне одному кажется, что автор проводит знак равенства между асинхронизмом и параллелизмом?

Кажется. Я сам перечитываю и у меня есть такое ощущение, но нет, я просто хотел показать, как где-то любая параллельная работа выполняется красиво, а где-то — нет

Асинхронизм(отложенные вычисления) может/могут быть построен/ы на многопоточности(java case) а могут и на EventLoop(который так же часто называют частной надстройкой поверх многопоточности) или на связке компилятор/"многопоточность" (.net case). В питоне отложенные вычисления базируются на EventLoop, но если я не ошибаюсь, то за все годы многопоточность в питон так и не завезли, есть многозадачность, но это немного не то.

Многопоточность в Python была всегда, другой вопрос, что эти потоки никогда не выполнялись параллельно
Как раз недавно столкнулся с необходимостью создать синхронный и асинхронный интерфейс к своей питоновской либе: github.com/pbelskiy/helix-swarm

Нет как таковой проблемы дублирования кода, код API одинаковый, разные только адаптеры (requests и aiohttp)
Sign up to leave a comment.

Articles