getattr/setattr/hasattr/delattr是否线程安全?

6

看看这个单例模式的实现:

if not hasattr(Singleton, "_instance"):                                    
    with Singleton._instance_lock:                                         
        if not hasattr(Singleton, "_instance"):                            
            Singleton._instance = Singleton()                                 
return Singleton._instance                                      

似乎 "Singleton._instance = .." (类似于 setattr)和 hasattr 是原子的。或者说由于 setattr 的存在,hasattr 不会导致崩溃。

但我找不到任何支持上述“似乎”的证据。


为什么不在模块导入时创建单例? - Ignacio Vazquez-Abrams
我并没有关注单例模式,而是getattr / setattr / hasattr / delattr。它是线程安全的吗? - hello.co
1个回答

7
通常情况下,如果您调用的对象未在Python中实现__getattr__, __delattr____setattr__钩子,则hasattrgetattrdelattrsetattr都是原子操作。
对于Python线程而言,任何单独的字节码都是原子操作。Python评估循环在解释操作码时会获取全局解释器锁(GIL)。
您需要查看字节码以确定边界所在:
>>> def foo():
...     if not hasattr(Singleton, "_instance"):
...         with Singleton._instance_lock:
...             if not hasattr(Singleton, "_instance"):
...                 Singleton._instance = Singleton()
...     return Singleton._instance
... 
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (hasattr)
              3 LOAD_GLOBAL              1 (Singleton)
              6 LOAD_CONST               1 ('_instance')
              9 CALL_FUNCTION            2
             12 POP_JUMP_IF_TRUE        64

  3          15 LOAD_GLOBAL              1 (Singleton)
             18 LOAD_ATTR                2 (_instance_lock)
             21 SETUP_WITH              35 (to 59)
             24 POP_TOP             

  4          25 LOAD_GLOBAL              0 (hasattr)
             28 LOAD_GLOBAL              1 (Singleton)
             31 LOAD_CONST               1 ('_instance')
             34 CALL_FUNCTION            2
             37 POP_JUMP_IF_TRUE        55

  5          40 LOAD_GLOBAL              1 (Singleton)
             43 CALL_FUNCTION            0
             46 LOAD_GLOBAL              1 (Singleton)
             49 STORE_ATTR               3 (_instance)
             52 JUMP_FORWARD             0 (to 55)
        >>   55 POP_BLOCK           
             56 LOAD_CONST               0 (None)
        >>   59 WITH_CLEANUP        
             60 END_FINALLY         
             61 JUMP_FORWARD             0 (to 64)

  6     >>   64 LOAD_GLOBAL              1 (Singleton)
             67 LOAD_ATTR                3 (_instance)
             70 RETURN_VALUE        

故事并未结束;hasattr 使用 getattr()(测试异常),进而可以调用 Python 的 __getattr__ 钩子。同样地,STORE_ATTR 操作码可能会调用 Python 的 __setattr__ 钩子实现。在这两种情况下,GIL 将再次被释放。

对于默认实现(Singleton 不实现这些钩子),操作是原子性的,因为 Python C 代码处理整个操作而不会回退到 Python,因此评估循环(其中 GIL 可能会被释放并再次锁定另一个线程)。

当然,您仍然可能需要处理自定义 C 库,在 对象协议操作期间释放锁。这将是一件不寻常的事情。


我不明白。CALL_FUNCTION 怎么可能是原子的呢?它并没有在整个函数的生命周期内获取 GIL,对吧? - georg
@thg435: 没错,我需要加上那个。 :-) - Martijn Pieters
更新了;我在火车和飞机上随时随地工作,我的注意力不够集中。 - Martijn Pieters
这不是大部分都特定于CPython吗?我认为应该提到这一点。 - Wessie
@Wessie:是的,GIL完全是CPython特有的。PyPy、Jython和IronPython不在此范围内。 - Martijn Pieters
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接