我想我可能漏掉了什么,这看起来很正确,但我找不到一种方法来做到这一点。
假设您在Python中有一个纯函数:
from math import sin, cos
def f(t):
x = 16 * sin(t) ** 3
y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)
return (x, y)
是否有内置的功能或库,可以在函数执行期间提供某种包装器以释放GIL?
我想到的是类似于以下的东西:
from math import sin, cos
from somelib import pure
@pure
def f(t):
x = 16 * sin(t) ** 3
y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)
return (x, y)
我为什么认为这可能会有用?
因为多线程目前只对I/O绑定的程序有吸引力,但一旦它们变成长时间运行的函数,多线程也将成为吸引人的选择。像下面这样做:
from math import sin, cos
from somelib import pure
from asyncio import run, gather, create_task
@pure # releases GIL for f
async def f(t):
x = 16 * sin(t) ** 3
y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
return (x, y)
async def main():
step_size = 0.1
result = await gather(*[create_task(f(t / step_size))
for t in range(0, round(10 / step_size))])
return result
if __name__ == "__main__":
results = run(main())
print(results)
当然,
multiprocessing
提供了 Pool.map
函数,可以实现类似的功能。然而,如果该函数返回非基本数据类型或复杂数据类型,则工作线程必须将其序列化,主进程必须反序列化并创建新对象,从而创建必要的副本。使用线程时,子线程传递指针,主线程只需拥有该对象即可。速度更快(而且更清洁?)。为了将这个问题与我几周前遇到的一个实际问题联系起来:我正在进行一个强化学习项目,其中涉及构建一个类似象棋的游戏的人工智能。为此,我模拟了 AI 自己对局超过 100,000 次,每次返回生成的状态序列(一个
numpy
数组)。生成这些游戏是在一个循环中运行的,并且每次使用这些数据来创建一个更强的 AI 版本。在这里,为每个游戏在主进程中重新创建 ("malloc
") 状态序列是瓶颈所在。我尝试重用现有对象,但由于许多原因,这是不好的想法,效果不佳。编辑:这个问题与 How to run functions in parallel? 不同,因为我不仅仅是寻找任何一种并行执行代码的方法(我知道可以通过多种方式实现,例如通过
multiprocessing
)。我正在寻找一种让解释器知道当这个函数在并行线程中执行时不会发生任何问题的方式。
t
是什么。如果它是一个单精度浮点数,释放和重新获取 GIL 的成本将高于执行 5 次三角函数操作和几个基本数学操作的成本。即使在 C++ 中,你没有 GIL 问题,为这样小的任务创建新的future
也没有意义。当然,两个 AI 之间的象棋比赛则另当别论,但你需要 Tensorflow 来完成这项任务。GIL 不适用于在 GPU 上运行的代码。 - MSaltersimport
的情况。我认为可能已经有一个库可以解决这个问题,但我不知道。 - FirefoxMetzger