在 python 中运行时仅运行一次函数

问题描述 投票:0回答:1

需要将一些数据加载到内存中。为此,我需要确保执行此操作的函数在运行时仅运行一次,无论调用多少次。

我正在使用装饰器以线程安全的方式执行此操作。 这是我正在使用的代码:

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 python-3.x
1个回答
0
投票

不使用锁实现此目的的一种方法是使用一些行为类似于原子测试和设置的操作。

有趣的是,这个官方 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)使用但不使用异常的原子操作,那么我很高兴知道!

© www.soinside.com 2019 - 2024. All rights reserved.