Почему эта одноэлементная реализация "не безопасна для потоков"? - PullRequest
0 голосов
/ 28 мая 2018

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)

Я нашел код здесь: Существует ли простой, элегантный способ определения синглетонов?

Комментарийвверху написано:

[Это] не-потокобезопасный вспомогательный класс для упрощения реализации синглетонов.

К сожалению, у меня недостаточно опыта многопоточностичтобы увидеть «нить-небезопасность» сам.


2.Вопросы

Я использую этот @Singleton декоратор в многопоточном приложении Python.Я беспокоюсь о потенциальных проблемах со стабильностью.Поэтому:

  1. Есть ли способ сделать этот код полностью поточно-ориентированным?

  2. Если предыдущий вопрос не имеет решения (или если егорешение слишком громоздкое), какие меры предосторожности я должен предпринять, чтобы оставаться в безопасности?

  3. @ Аран-Фей указал, что кодировщик плохо закодирован.Любые улучшения, конечно, очень приветствуются.


Настоящим я предоставляю свои текущие настройки системы:
> Python 3.6.3
> Windows 10,64-битный

Ответы [ 3 ]

0 голосов
/ 11 апреля 2019

Если вы беспокоитесь о производительности, вы можете улучшить решение принятого ответа, используя шаблон проверки-блокировки-проверки , чтобы минимизировать получение блокировки:

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)
0 голосов
/ 17 июня 2019

Я публикую это просто для упрощения предлагаемого решения @ OlivierMelançon и @ se7entyse7en: нет накладных расходов на import functools и перенос.

import threading</p> <p>lock = threading.Lock()</p> <p>class SingletonOptmizedOptmized(type): _instances = {} def <strong>call</strong>(cls, *args, **kwargs): if cls not in cls._instances: with lock: if cls not in cls._instances: cls._instances[cls] = super(SingletonOptmizedOptmized, cls).<strong>call</strong>(*args, **kwargs) return cls._instances[cls]</p> <p>class SingletonClassOptmizedOptmized(metaclass=SingletonOptmizedOptmized): pass

Разница:

>>> timeit('SingletonClass()', globals=globals(), number=1000000)
0.4635776
>>> timeit('SingletonClassOptmizedOptmized()', globals=globals(), number=1000000)
0.192263300000036
0 голосов
/ 28 мая 2018

Я предлагаю вам выбрать лучшую реализацию синглтона.Наиболее часто используется реализация на основе метакласса .

Что касается безопасности потоков, ни ваш подход, ни какой-либо из предложенных в приведенной выше ссылке не являются потокобезопасными: это всегдавозможно, что поток читает, что существующего экземпляра нет, и начинает его создавать, но другой поток делает то же самое до сохранения первого экземпляра.

Вы можете использовать предложенный декоратор .в этом ответе для защиты метода __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
...