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

K.M*_*ier 5 python singleton multithreading python-3.x python-3.6

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)
Run Code Online (Sandbox Code Playgroud)

我在这里找到了代码: 是否有一种简单,优雅的方式来定义单身人士?

顶部的评论说:

[这是]一个非线程安全的助手类,可以轻松实现单例.

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

 

2.问题

我@Singleton在多线程Python应用程序中使用此装饰器.我担心潜在的稳定性问题.因此:

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

  2. 如果上一个问题没有解决方案(或者解决方案过于繁琐),我应该采取哪些预防措施来保证安全?

  3. @ Aran-Fey指出装饰器编码很糟糕.当然,非常感谢任何改进.


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

Oli*_*çon 9

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

至于线程安全,也不是你的方法或上面链接中建议的任何方法都是线程安全的:线程总是可以读取没有现有实例并开始创建一个,但另一个线程之前也是如此存储了第一个实例.

您可以使用此答案中建议的装饰器来保护__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
Run Code Online (Sandbox Code Playgroud)

  • 代替装饰器`@synchronized(lock)` 简单地使用`with lock:` 怎么样? (2认同)
  • @OlivierMelançon `with lock:` 在这里是相同的 :) 1 个块,它只是显示/分离受锁保护的内容,并且没有导入 functools 和包装的开销。我们不使用`@open_file()`而是`with (open()):` (2认同)

se7*_*7en 8

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

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
Run Code Online (Sandbox Code Playgroud)

这是区别:

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)
Run Code Online (Sandbox Code Playgroud)

  • 获取 **名称错误:名称“同步”未定义**。有什么要进口的吗? (2认同)