Зачем нам нужен 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 не должен быть вашим выбором.

Вернуться на верх