代码之家  ›  专栏  ›  技术社区  ›  K.Mulier

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

  •  4
  • K.Mulier  · 技术社区  · 7 年前

    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。问题

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

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

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

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


    在此,我提供我当前的系统设置:
    >蟒蛇3.6.3
    >Windows 10,64位

    3 回复  |  直到 7 年前
        1
  •  9
  •   Aran-Fey Kevin    7 年前

    我建议您选择一个更好的单例实现。这个 metaclass-based implementation 是最常用的。

    至于线程安全,您的方法和上面链接中建议的任何方法都不是线程安全的:线程总是有可能读取到没有现有实例并开始创建实例,但是 another thread does the same 在存储第一个实例之前。

    您可以使用 decorator suggested in this answer 为了保护 __call__ 基于元类的带有锁的单例类的方法。

    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
    
        2
  •  2
  •   se7entyse7en    7 年前

    如果您关心性能,可以使用 check-lock-check pattern 要最小化锁定采集:

    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)
    
        3
  •  0
  •   martin-voj    6 年前

    我发布这个只是为了简化@oliviermelan在和@se7entyse7en上提出的解决方案:没有开销 import functools 和包装。

    import threading

    lock=threading.lock()。

    类singletOnOptmizedOptmized(类型): _实例= DEF 呼叫 (cls,*args,**kwargs): 如果cls不在cls.\u实例中: 带锁: 如果cls不在cls.\u实例中: cls._instances[cls]=super(singletonOptmizedOptmized,cls)。 呼叫 (*args,**kwargs) 返回cls.\u实例[cls]

    类singletonClassoptmizedOptmized(元类=singletonOptmizedOptmized): 通过

    差异:

    >>> timeit('SingletonClass()', globals=globals(), number=1000000)
    0.4635776
    >>> timeit('SingletonClassOptmizedOptmized()', globals=globals(), number=1000000)
    0.192263300000036