can*_*dry 7 python concurrency
我刚刚阅读了这篇关于懒惰初始化对象属性的配方的博文.我是一个正在恢复的java程序员,如果这段代码被翻译成java,它将被视为竞争条件(双重检查锁定).为什么它在python中工作?我知道python中有一个线程模块.解释器是否秘密添加了锁以使此线程安全?
规范的线程安全初始化在Python中是如何看待的?
此代码不是线程安全的。
您可以通过逐步检查字节码来检查线程安全性,例如:
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
Run Code Online (Sandbox Code Playgroud)
但是,我应该警告,字节码可能会随时间而变化,线程安全性可能取决于您使用的python(cpython,jython,ironpython等)
因此,一般建议,如果您需要线程安全,请使用同步机制:锁,队列,信号量等。
您提到的描述符的线程安全性可以这样来实现:
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__]
Run Code Online (Sandbox Code Playgroud)
对于规范的线程安全初始化,您需要编写一个元类,该元类在创建时会获取锁,并在实例创建后释放:
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)
'''
Run Code Online (Sandbox Code Playgroud)
最后,如果您需要更简单的解决方案,只需创建通用的初始化锁并使用它来创建实例:
from threading import Lock
MyCls._inst_lock = Lock() # monkey patching | or subclass if hate it
...
with MyCls._inst_lock:
myinst = MyCls()
Run Code Online (Sandbox Code Playgroud)
但是,很容易忘记,这可能会带来非常有趣的调试时间。也可以编写类装饰器,但我认为这不会比元类解决方案更好。