需要将一些数据加载到内存中。为此,我需要确保执行此操作的函数在运行时仅运行一次,无论调用多少次。
我正在使用装饰器以线程安全的方式执行此操作。 这是我正在使用的代码:
import threading
# Instantiating a lock object
# This will be used to ensure that multiple parallel threads will not be able to run the same function at the same time
# in the @run_once decorator written below
__lock = threading.Lock()
def run_once(f):
"""
Decorator to run a function only once.
:param f: function to be run only once during execution time despite the number of calls
:return: The original function with the params passed to it if it hasn't already been run before
"""
def wrapper(*args, **kwargs):
"""
The actual wrapper where the business logic to call the original function resides
:param args:
:param kwargs:
:return: The original function unless the wrapper has been run already
"""
if not wrapper.has_run:
with __lock:
if not wrapper.has_run:
wrapper.has_run = True
return f(*args, **kwargs)
wrapper.has_run = False
return wrapper
我是否需要在锁外部和内部时对
has_run
标志进行双重检查,以便不会在过时的对象上进行读取?
不使用锁实现此目的的一种方法是使用一些行为类似于原子测试和设置的操作。
有趣的是,这个官方 Python FAQ 页面 说:
Python 只提供在字节码指令之间切换线程
内部使用全局解释器锁(GIL)来确保Python VM中一次只有一个线程运行
这意味着
从 Python 程序的角度来看,每个字节码指令以及从每个指令到达的所有 C 实现代码都是原子的。
一个有趣的操作是
del
,反汇编后可以看到它是原子的(根据上面的定义,是一条字节码指令):
import dis
def my_deleter():
del a
print(dis.dis(my_deleter))
# 0 DELETE_FAST 0 (a) <--- single bytecode instruction => atomic!
# 2 LOAD_CONST 0 (None)
# 4 RETURN_VALUE
您可能知道,
del
只是从对象中删除名称绑定(如果存在) - 否则,它会抛出NameError
。
例如,我们可以滥用它来构建仅在第一次返回 True 的 RunOnce
令牌。请注意,我还添加了一个快速路径,以便仅在罕见的最坏情况下处理异常。
class RunOnce:
def __init__(self):
self.flag = None
self.use_fast_path = False
def __call__(self):
if self.use_fast_path:
return False
try:
del self.flag
self.use_fast_path = True
return True
except NameError:
return False
tok = RunOnce()
print(tok()) # True
print(tok()) # False
print(tok()) # False
如果您发现任何可以(ab)使用但不使用异常的原子操作,那么我很高兴知道!