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

Why is this singleton implementation “not thread safe”?

1。@singleton装饰师

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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)

我在这里找到了代码:有没有一个简单,优雅的方式来定义单件?

上面的评论说:

[This is] a non-thread-safe helper class to ease implementing singletons.

不幸的是,我没有足够的多线程经验来看到"线程不安全"自己。

nbsp;

2。问题

我在多线程的python应用程序中使用这个@Singleton装饰器。我担心潜在的稳定问题。因此:

  • 有没有一种方法可以使这个代码完全线程安全?

  • 如果前面的问题没有解决方案(或者解决方案过于繁琐),我应该采取什么预防措施来保持安全?

  • @阿兰·费伊指出,装修工的代码写得很糟糕。当然,任何改进都非常感谢。

  • 在此,我提供我当前的系统设置:&Python3.6.3&64位Windows 10


    我建议您选择一个更好的单例实现。基于元类的实现是最常用的。

    至于线程安全,您的方法和上面链接中建议的任何方法都不是线程安全的:线程总是可能读取不存在的实例并开始创建一个实例,但另一个线程在存储第一个实例之前也会这样做。

    您可以使用这个答案中建议的装饰器来保护基于元类的带有锁的单例类的__call__方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import functools
    import threading

    lock = threading.Lock()


    def synchronized(lock):
       """ Synchronization decorator"""
        def wrapper(f):
            @functools.wraps(f)
            def inner_wrapper(*args, **kw):
                with lock:
                    return f(*args, **kw)
            return inner_wrapper
        return wrapper


    class Singleton(type):
        _instances = {}

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


    class SingletonClass(metaclass=Singleton):
        pass


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

    区别在于:

    1
    2
    3
    4
    5
    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)