文件说:
Django 中需要这样做的原因是许多库,特别是数据库适配器,要求在创建它们的同一线程中访问它们。此外,许多现有的 Django 代码假设它们都在同一线程中运行,例如中间件将内容添加到请求中以便稍后在视图中使用。
但是另一个问题当两个asyncio任务访问同一个awaitable对象时安全吗?说python的asyncio是线程安全的。
据我所知,由于 GIL 仍然存在,从多个线程访问一个对象应该是线程安全的。
任何人都可以举一个最小的例子来说明为什么我们必须在 django 或其他异步应用程序中使用
await sync_to_async(foo)()
而不是直接使用 foo()
吗?
这个问题(sync_to_async如何将同步函数转换为异步)并没有回答我的问题,在这个例子中使用
sync_to_async
没有显示出任何显着差异,second_job内部函数调用的顺序是相同的。并且与避免严重冲突无关。
我知道谁
sync_to_async
在工作,但是为什么我们需要在异步上下文中的新线程中运行同步函数?
经过评论的反复讨论,我觉得我很清楚你想要什么样的解释。
如果您继续阅读您引用的文档,在接下来的段落中,它写道:
我们没有引入此代码的潜在兼容性问题,而是选择添加此模式,以便所有现有的 Django 同步代码在同一线程中运行,从而与异步模式完全兼容。请注意,同步代码始终位于与调用它的任何异步代码不同的线程中,因此您应该避免传递原始数据库句柄或其他线程敏感引用。
(上面的重点是我的,下面的引号也是如此)。
除了确保同步(阻塞)函数调用不会导致主工作线程阻塞,因为这会阻止其他所有事情完成(包括可能同时发生的其他异步 IO),上面解释了Django 文档坚持必须使用
sync_to_async
的真正原因。
您的问题中需要解决的一点是:
...但是为什么我们需要在异步上下文中的新线程中运行同步函数?
您的假设是错误的,至少在 Django 在异步上下文中运行时是如何设置的。 除了上面我强调的重点之外,还引用了您引用的段落之前的几段内容:
如果您使用
或类似的,它将回退到在单个共享线程中运行线程敏感函数,但这不会是主线程。asyncio.run()
再次注意这里的关键字:“单个共享线程”,不是新线程。 至于“为什么”,这是为了确保遗留同步应用程序可能不是线程安全的,或者可以在多线程环境中使用但具有不能违反的不变量的东西,例如SQLite,其中它“可以被多个线程安全地使用,前提是没有单个数据库连接或从数据库连接派生的任何对象(例如准备好的语句)同时在两个或多个线程中使用。”
您问题中的另一点需要解决:
据我所知,由于 GIL 仍然存在,从多个线程访问一个对象应该是线程安全的。
再次强调,GIL 可能只保护纯 Python 代码,但它对 Python 使用的 C 库绝对没有任何作用,因为 SQLite 指定的不变量不能由 GIL 维护。
为了确保更安全地使用 SQLite 连接,通常使用 threadlocals 来建立连接,其中该连接对象通常不容易在不同的线程中使用。而在 Django 的 ORM 使用中,根本不支持异步,引用:
我们仍在致力于对 ORM 和 Django 其他部分的异步支持。您可以期待在未来的版本中看到这一点。现在,您可以使用
适配器与 Django 的同步部分进行交互。还有一系列可供您集成的异步原生 Python 库。sync_to_async()
我会假设 Django 在专用同步线程中使用 threadlocals(我不会花时间来查找这一点;你可以弄清楚这一点),以确保在该专用线程内设置的连接不会被复制/克隆这将违反 SQLite 的线程安全不变量。因此,要访问数据库连接对象,必须使用
sync_to_async()
。
最后,Django 的设置方式与性能无关,而是与非同步和非线程安全代码的互操作性有关,这些代码遍布其自身和 Python 生态系统中的其他地方。
所以,如果你想要一些“健康”的东西,根据你的评论中给出的定义,Django 不应该是你的选择。