17

我有一些使用交互式加载 python 模块的函数__import__

我最近偶然发现了一些关于 Python 中的“导入锁”的文章,即专门用于导入的锁(不仅仅是 GIL)。但是这篇文章很旧,所以也许这不再是真的了。

这让我想知道在线程中导入的做法。

  1. import/__import__线程安全吗?
  2. 他们可以创建死锁吗?
  3. 它们会导致线程应用程序中的性能问题吗?

编辑 2012 年 9 月 12 日

感谢 Soravux 的精彩回复。所以导入是线程安全的,我不担心死锁,因为__import__在我的代码中使用的函数不会相互调用。

即使模块已经导入,您是否知道是否获得了锁?如果是这种情况,我可能应该在 sys.modules 中查看模块是否已经被导入,然后再调用__import__.

当然,这在 CPython 中应该不会有太大的不同,因为无论如何都有 GIL。但是,它可能会对 Jython 或无堆栈 python 等其他实现产生很大影响。

编辑 2012 年 9 月 19 日

关于 Jython,这是他们在文档中所说的:

http://www.jython.org/jythonbook/en/1.0/Concurrency.html#module-import-lock

然而,Python 确实定义了一个由 Jython 实现的模块导入锁。每当导入任何名称时都会获取此锁。无论导入是通过 import 语句、等效的__import__内置函数还是相关代码,都是如此。需要注意的是,即使相应的模块已经被导入,模块导入锁仍然会被获取,即使只是短暂的。

因此,在进行导入之前检查 sys.modules 以避免获取锁似乎是有意义的。你怎么看?

4

2 回答 2

18

更新:从 Python 3.3 开始,导入锁是每个模块而不是全局的,并且imp不推荐使用importlib. 有关更改日志此问题票的更多信息。

下面的原始答案早于 Python 3.3

普通导入是线程安全的,因为它们在执行之前获取一个导入锁,并在导入完成后释放它。如果您使用可用的钩子添加自己的自定义导入,请务必向其中添加此锁定方案。Python 中的锁定设施可以由imp模块 ( imp.lock_held()/ acquire_lock()/ release_lock()) 访问。编辑:自 Python 3.3 以来已弃用,无需手动处理锁。

除了已知的循环依赖(模块a导入模块b导入模块a)之外,使用此导入锁不会产生任何死锁或依赖错误。编辑: Python 3.3 更改了每个模块的锁定机制,以防止循环导入导致的死锁。

存在多种创建新进程或线程的方法,例如forkclone(假设是 Linux 环境)。在创建新进程时,每种方式都会产生不同的内存行为。默认情况下,fork 会复制大多数内存段(数据(通常是 COW)、堆栈、代码、堆),实际上不会在子节点和父节点之间共享其内容。a clone(通常称为线程,这是 Python 用于线程的)的结果与其父级共享所有内存段,但堆栈除外。Python 中的导入机制使用放在堆栈上的全局命名空间,因此在其线程之间使用共享段。这意味着所有的内存修改(堆栈除外)都由一个import在一个线程中将对其所有其他相关线程和父线程可见。如果导入的模块是纯 Python 的,则它在设计上是线程安全的。如果导入的模块使用非 Python 库,请确保它们是线程安全的,否则在多线程 Python 代码中造成混乱。

顺便说一句,Python 中的线程程序会受到GIL的影响,除非您的程序受 I/O 限制或依赖于 C 或外部线程安全库(因为它们应该在执行之前释放 GIL),否则不会获得太多性能提升。由于这个 GIL,在两个线程中运行相同的导入 Python 函数不会同时执行。请注意,这只是 CPython 的限制,Python 的其他实现可能有不同的行为。

回答您的编辑:导入的模块都由 Python 缓存。如果模块已经加载到缓存中,它将不会再次运行,并且导入语句(或函数)将立即返回。您不必自己在 sys.modules 中实现缓存查找,Python 会为您执行此操作并且不会imp锁定任何内容,除了用于 sys.modules 查找的 GIL。

回答您的第二次编辑:与尝试优化对我使用的库(在本例中为标准库)的调用相比,我更喜欢维护更简单的代码。理由是执行某事所需的时间通常比导入执行该操作的模块所需的时间更重要。此外,在整个项目中维护此类代码所需的时间远高于执行所需的时间。这一切都归结为:“程序员的时间比 CPU 的时间更有价值”。

于 2012-09-12T15:04:24.020 回答
2

我在官方文档中找不到有关此问题的答案,但似乎在某些 CPython 3.x 版本中,__import__调用不是线程安全的,并且可能导致死锁。请参阅:https ://bugs.python.org/issue38884 。

于 2019-11-25T22:27:50.067 回答