Этот код не является потокобезопасным.
Определение безопасности потока
Вы можете проверить безопасность потоков, пройдя байт-код, например:
from dis import dis
dis('a = [] \n'
'a.append(5)')
# Here you could see that it's thread safe
## 1 0 BUILD_LIST 0
## 3 STORE_NAME 0 (a)
##
## 2 6 LOAD_NAME 0 (a)
## 9 LOAD_ATTR 1 (append)
## 12 LOAD_CONST 0 (5)
## 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
## 18 POP_TOP
## 19 LOAD_CONST 1 (None)
## 22 RETURN_VALUE
dis('a = [] \n'
'a += 5')
# And this one isn't (possible gap between 15 and 16)
## 1 0 BUILD_LIST 0
## 3 STORE_NAME 0 (a)
##
## 2 6 LOAD_NAME 0 (a)
## 9 LOAD_CONST 0 (5)
## 12 BUILD_LIST 1
## 15 BINARY_ADD
## 16 STORE_NAME 0 (a)
## 19 LOAD_CONST 1 (None)
## 22 RETURN_VALUE
Однако я должен предупредить, что байт-код может со временем меняться, а безопасность потоков может зависеть от используемого вами Python (cpython, jython, ironpython и т. Д.)
Итак, общая рекомендация: если вам когда-либо понадобится безопасность потоков, используйте механизмы синхронизации: блокировки, очереди, семафоры и т. Д.
Поточно-ориентированная версия LazyProperty
Потоковая безопасность для дескриптора, который вы упомянули, может быть приведена так:
from threading import Lock
class LazyProperty(object):
def __init__(self, func):
self._func = func
self.__name__ = func.__name__
self.__doc__ = func.__doc__
self._lock = Lock()
def __get__(self, obj, klass=None):
if obj is None: return None
# __get__ may be called concurrently
with self.lock:
# another thread may have computed property value
# while this thread was in __get__
# line below added, thx @qarma for correction
if self.__name__ not in obj.__dict__:
# none computed `_func` yet, do so (under lock) and set attribute
obj.__dict__[self.__name__] = self._func(obj)
# by now, attribute is guaranteed to be set,
# either by this thread or another
return obj.__dict__[self.__name__]
Каноническая поточно-ориентированная инициализация
Для канонической поточно-ориентированной инициализации вам необходимо кодировать метакласс, который получает блокировку во время создания и освобождает после создания экземпляра:
from threading import Lock
class ThreadSafeInitMeta(type):
def __new__(metacls, name, bases, namespace, **kwds):
# here we add lock to !!class!! (not instance of it)
# class could refer to its lock as: self.__safe_init_lock
# see namespace mangling for details
namespace['_{}__safe_init_lock'.format(name)] = Lock()
return super().__new__(metacls, name, bases, namespace, **kwds)
def __call__(cls, *args, **kwargs):
lock = getattr(cls, '_{}__safe_init_lock'.format(cls.__name__))
with lock:
retval = super().__call__(*args, **kwargs)
return retval
class ThreadSafeInit(metaclass=ThreadSafeInitMeta):
pass
######### Use as follows #########
# class MyCls(..., ThreadSafeInit):
# def __init__(self, ...):
# ...
##################################
'''
class Tst(ThreadSafeInit):
def __init__(self, val):
print(val, self.__safe_init_lock)
'''
Что-то совершенно отличное от решения метаклассов
И, наконец, если вам нужно более простое решение, просто создайте общую блокировку инициализации и создайте ее с помощью экземпляров:
from threading import Lock
MyCls._inst_lock = Lock() # monkey patching | or subclass if hate it
...
with MyCls._inst_lock:
myinst = MyCls()
Однако легко забыть, что может привести к очень интересным временам отладки.
Также возможно закодировать декоратор класса, но на мой взгляд, это будет не лучше, чем решение метакласса.