为什么这个单例实现“不是线程安全的”?

新手上路,请多包涵

1. @Singleton 装饰器

我找到了一种优雅的方式来装饰 Python 类,使其成为 singleton 。该类只能产生一个对象。每个 Instance() 调用返回相同的对象:

 class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Also, the decorated class cannot be
    inherited from. Other than that, there are no restrictions that apply
    to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

我在这里找到了代码: Is there a simple, elegant way to define singletons?

顶部的评论说:

[这是]一个非线程安全的辅助类,用于简化单例的实现。

不幸的是,我自己没有足够的多线程经验来了解“线程不安全”。

2.问题

我在多线程 Python 应用程序中使用这个 @Singleton 装饰器。我担心潜在的稳定性问题。所以:

  1. 有没有办法让这段代码完全线程安全?

  2. 如果上一个问题没有解决方案(或者它的解决方案太麻烦),我应该采取什么预防措施来保证安全?

  3. @Aran-Fey 指出装饰器编码错误。当然非常感谢任何改进。


我在此提供我当前的系统设置:

> Python 3.6.3

> Windows 10,64 位

原文由 K.Mulier 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 868
2 个回答

我建议你选择一个更好的单例实现。在我看来 ,基于元类的实现 是最优雅的。

至于线程安全,你的方法和上面链接中建议的任何方法都不是线程安全的:一个线程总是有可能读取到没有现有实例并开始创建一个,但 另一个线程之前做同样的 事情第一个实例已存储。

您可以使用 with lock 控制器 来保护 __call__ 带有锁的基于元类的单例类的方法。

 import threading

lock = threading.Lock()

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            with lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=Singleton):
    pass

正如 se7entyse7en 所建议的,您可以使用 check-lock-check pattern 。由于单例只创建一次,您唯一关心的是必须锁定初始实例的创建。尽管完成此操作后,检索实例根本不需要锁定。出于这个原因,我们接受在第一次调用时重复检查,以便所有进一步的调用甚至不需要获取锁。

原文由 Olivier Melançon 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您担心性能,您可以通过使用 检查锁定检查模式 来最小化锁定获取来改进已接受答案的解决方案:

 class SingletonOptmized(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._locked_call(*args, **kwargs)
        return cls._instances[cls]

    @synchronized(lock)
    def _locked_call(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonOptmized, cls).__call__(*args, **kwargs)

class SingletonClassOptmized(metaclass=SingletonOptmized):
    pass

这是区别:

 In [9]: %timeit SingletonClass()
488 ns ± 4.67 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [10]: %timeit SingletonClassOptmized()
204 ns ± 4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

原文由 se7entyse7en 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题