Зачем нам нужен sync_to_async в Django? [дубликат]
В документе сказано:
Причина, по которой это необходимо в Django, заключается в том, что многие библиотеки, в частности адаптеры баз данных, требуют, чтобы доступ к ним осуществлялся в том же потоке, в котором они были созданы. Также множество существующего кода Django предполагает, что все выполняется в одном потоке, например, промежуточное ПО, добавляющее вещи в запрос для последующего использования в представлениях.
Но еще один вопрос Безопасно ли, когда две задачи asyncio обращаются к одному и тому же awaitable объекту? Говорят, что asyncio в Python является потокобезопасным.
Как я знаю, поскольку GIL все еще существует, доступ к одному объекту из нескольких потоков должен быть потокобезопасным.
Может ли кто-нибудь привести минимальный пример того, почему мы должны использовать await sync_to_async(foo)()
вместо прямого foo()
в django или других async-приложениях?
После того, как в комментариях мы обсудили все эти вопросы, я понял, какое объяснение вам нужно.
Если вы продолжаете читать документацию, которую вы процитировали, то в следующем за ней абзаце написано:
Вместо того, чтобы создавать потенциальные проблемы совместимости с этим кодом, мы решили добавить этот режим, чтобы весь существующий код синхронизации Django выполнялся в том же потоке и, таким образом, был полностью совместим с режимом async. Обратите внимание, что код синхронизации всегда будет находиться в потоке, отличном от вызывающего его кода async, поэтому вам следует избегать передачи необработанных дескрипторов баз данных или других чувствительных к потоку ссылок.
(Подчеркивания мои в вышеприведенном тексте, и так же в цитатах ниже).
Помимо обеспечения того, чтобы синхронные (блокирующие) вызовы функций не приводили к блокировке основного рабочего потока (потоков), поскольку это помешает выполнению всего остального (включая другие асинхронные операции ввода-вывода, которые могут происходить одновременно), вышесказанное объясняет реальную причину, по которой документация Django настаивает на том, что sync_to_async
должен использоваться.
На один момент из вашего вопроса нужно обратить внимание:
... но почему мы должны запускать функцию синхронизации в новом потоке в контексте async?
Ваше предположение здесь неверно, по крайней мере, в том, как Django настроен, когда он работает в контексте async. Наряду с тем, что я выделил выше и процитировал пару абзацев до того, который вы процитировали:
Если вы используете
asyncio.run()
или что-то подобное, он вернется к выполнению чувствительных к потокам функций в одном общем потоке, но это не будет основной поток.
Опять же, обратите внимание на ключевые слова: «единый, общий поток», не новый поток. Что касается «почему», то это делается для того, чтобы обеспечить безопасность унаследованных синхронных приложений, которые могут быть небезопасны для потоков, или тех, которые могут использоваться в многопоточной среде, но имеют инварианты, которые нельзя нарушать, например SQLite, где он «может безопасно использоваться несколькими потоками при условии, что ни одно соединение с базой данных или любой объект, полученный из соединения с базой данных, например подготовленный оператор, не используется в двух или более потоках одновременно.»
Еще один момент из вашего вопроса требует рассмотрения:
И, как я знаю, поскольку GIL все еще существует, доступ к одному объекту из нескольких потоков должен быть потокобезопасным.
Опять же, GIL может защитить только чистый код Python, но он абсолютно ничего не делает с библиотеками C, которые использует Python, потому что инварианты, указанные SQLite , не могут быть поддержаны GIL.
Для обеспечения более безопасного использования соединения SQLite, типично использовать threadlocals для установления соединения, где этот объект соединения обычно не может быть легко использован в другом потоке. В то время как в ORM Django его использование просто пока не поддерживает async, цитата:
Мы продолжаем работать над поддержкой async для ORM и других частей Django. Вы можете ожидать ее появления в будущих релизах. Пока же вы можете использовать адаптер
sync_to_async()
для взаимодействия с синхронизируемыми частями Django. Существует также целый ряд асинхронных библиотек Python, с которыми вы можете интегрироваться.
Предположу, что Django использует threadlocals внутри выделенного потока синхронизации (я не трачу время на поиски, вы и сами можете разобраться), чтобы гарантировать, что соединение, установленное внутри этого выделенного потока, не будет скопировано/клонировано оттуда, что нарушит инвариант потокобезопасности SQLite. Таким образом, чтобы иметь доступ к соединению с базой данных или делать ORM в Django, использование sync_to_async()
является обязательным.
Наконец, то, как Django это устроил, не имеет никакого отношения к производительности, но имеет отношение к взаимодействию с несинхронным и непотокобезопасным кодом, который встречается как в самом Python, так и в других частях его экосистемы.
Итак, если вы хотите что-то «здоровое» в соответствии с определениями, приведенными в вашем комментарии, Django не должен быть вашим выбором.