предисловие
В сегодняшней дискуссии в группе обсуждался паттерн singleton, который должен быть наиболее знакомым паттерном проектирования.
Проще говоря, шаблон singleton должен гарантировать, что существует только один экземпляр определенного экземпляра во всем жизненном цикле проекта, и это тот же самый экземпляр, который используется в любом месте проекта.
Хотя паттерн singleton прост, некоторые дорвеи все же есть, и мало кто знает эти дорвеи.
пограничный случай
Существует множество способов реализации одноэлементного шаблона в Python, наиболее часто я использовал следующий.
class Singleton(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
Есть две проблемы с этим письмом.
1. Параметры не могут быть переданы при создании экземпляра класса, соответствующего одноэлементному режиму.Расширьте приведенный выше код в следующей форме.
class Singleton(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init(self, x, y):
self.x = x
self.y = y
s = Singleton(1,2)
броситTypeError: object.__new__() takes exactly one argument (the type to instantiate)Ошибка
2. Когда несколько потоков создают экземпляр класса Singleton, может быть создано несколько экземпляров, поскольку весьма вероятно, что несколько потоков одновременно определят, что cls._instance имеет значение None, и введут код для инициализации экземпляра.
Синглтон на основе блокировки синхронизации
Сначала рассмотрим вторую проблему, с которой сталкивается описанная выше реализация.
Поскольку в случае многопоточности и, следовательно, параметров нескольких экземпляров будут граничные условия, можно использовать блокировки синхронизации для разрешения многопоточных конфликтов.
import threading
# 同步锁
def synchronous_lock(func):
def wrapper(*args, **kwargs):
with threading.Lock():
return func(*args, **kwargs)
return wrapper
class Singleton(object):
instance = None
@synchronous_lock
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = object.__new__(cls, *args, **kwargs)
return cls.instance
В приведенном выше коде одноэлементный метод синхронизируется с помощью threading.Lock(), поэтому несколько экземпляров не будут создаваться перед несколькими потоками, поэтому вы можете просто протестировать его.
def worker():
s = Singleton()
print(id(s))
def test():
task = []
for i in range(10):
t = threading.Thread(target=worker)
task.append(t)
for i in task:
i.start()
for i in task:
i.join()
test()
После запуска идентификаторы напечатанных синглетонов одинаковы.
лучший способ
После добавления блокировки синхронизации нет большой проблемы, кроме того, что параметры нельзя передать, но есть ли лучшее решение? Есть ли реализация одноэлементного шаблона, которая может принимать параметры?
Прочтите официальную вики Python (wiki.Python.org/MO в/Python…
def singleton(cls):
cls.__new_original__ = cls.__new__
@functools.wraps(cls.__new__)
def singleton_new(cls, *args, **kwargs):
it = cls.__dict__.get('__it__')
if it is not None:
return it
cls.__it__ = it = cls.__new_original__(cls, *args, **kwargs)
it.__init_original__(*args, **kwargs)
return it
cls.__new__ = singleton_new
cls.__init_original__ = cls.__init__
cls.__init__ = object.__init__
return cls
@singleton
class Foo(object):
def __new__(cls, *args, **kwargs):
cls.x = 10
return object.__new__(cls)
def __init__(self, x, y):
assert self.x == 10
self.x = x
self.y = y
Декоратор одноэлементного класса определен в приведенном выше коде, и декоратор будет выполняться во время предварительной компиляции. Используя эту функцию, декоратор одноэлементного класса заменяет исходный класс.__new__и__init__метод, используйте метод singleton_new для создания экземпляра класса, в методе singleton_new сначала определите, существуют ли атрибуты класса__it__Атрибут, чтобы определить, создавать ли новый экземпляр, если да, то вызывать исходный экземпляр класса.__new__Метод создается и вызывает исходный__init__Метод передает параметры текущему классу, тем самым выполняя цель одноэлементного шаблона.
Этот метод позволяет одноэлементному классу принимать соответствующие параметры, но все же может быть несколько экземпляров при создании экземпляров несколькими потоками одновременно.В этом случае можно добавить блокировку синхронизации потоков.
def singleton(cls):
cls.__new_original__ = cls.__new__
@functools.wraps(cls.__new__)
def singleton_new(cls, *args, **kwargs):
# 同步锁
with threading.Lock():
it = cls.__dict__.get('__it__')
if it is not None:
return it
cls.__it__ = it = cls.__new_original__(cls, *args, **kwargs)
it.__init_original__(*args, **kwargs)
return it
cls.__new__ = singleton_new
cls.__init_original__ = cls.__init__
cls.__init__ = object.__init__
return cls
Дополнительные соображения о том, следует ли добавлять блокировки синхронизации
Если проекту не нужно использовать механизмы, связанные с потоками, а используются только блокировки потоков в синглтонах, в этом нет необходимости, и это замедлит скорость выполнения проекта.
Прочтите исходный код, относящийся к модулю потока CPython, вы обнаружите, что Python не инициализирует среду, связанную с потоком, в начале. Фрагмент кода выглядит следующим образом (я опустил много нерелевантного кода).
static PyObject *
thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
{
PyObject *func, *args, *keyw = NULL;
struct bootstate *boot;
unsigned long ident;
// 初始化多线程环境,解释器默认不初始化,只有用户使用时,才初始化。
PyEval_InitThreads(); /* Start the interpreter's thread-awareness */
// 创建线程
ident = PyThread_start_new_thread(t_bootstrap, (void*) boot);
// 返回线程id
return PyLong_FromUnsignedLong(ident);
}
Почему это происходит?
Потому что многопоточная среда запустит логику, связанную с блокировкой GIL, что повлияет на скорость работы программы Python. Многие простые программы Python не нуждаются в многопоточности. В настоящее время нет необходимости инициализировать среду, связанную с потоками. Программы Python будут работать быстрее без блокировок GIL.
Если в вашем проекте не используются многопоточные операции, синхронизированные блокировки не используются для реализации одноэлементного шаблона.
конец
В Интернете есть много статей, которые реализуют режим singleton в Python, вам нужно только судить о соответствующем методе реализации с двух точек зрения: можно ли гарантировать один экземпляр при многопоточности и можно ли передавать начальные параметры при singletoning .
Добро пожаловать в раздел «Ленивое программирование» и вместе исследуйте суть технологий.