我经常使用带有阻塞方法的Python对象,并且想要将这些方法转换成非阻塞版本。我经常执行以下模式:
以下是一个高度简化的示例,以说明我一直在使用的模式:
- 定义对象
- 定义一个函数,该函数创建对象实例并解析调用对象方法的命令
- 定义一个“父”对象,它创建一个子进程来运行步骤2中定义的函数,并复制原始对象的方法。
以下是一个高度简化的示例,以说明我一直在使用的模式:
import ctypes
import Queue
import multiprocessing as mp
class Hardware:
def __init__(
self,
other_init_args):
self.dll = ctypes.cll.LoadLibrary('hardware.dll')
self.dll.Initialize(other_init_args)
def blocking_command(self, arg_1, arg_2, arg_3):
"""
This command takes a long time to execute, and blocks while it
executes. However, while it's executing, we have to coordinate
other pieces of hardware too, so blocking is bad.
"""
self.dll.Takes_A_Long_Time(arg_1, arg_2, arg_3)
def change_settings(self, arg_1, arg_2):
"""
Realistically, there's tons of other functions in the DLL we
want to expose as methods. For this example, just one.
"""
self.dll.Change_Settings(arg_1, arg_2)
def close(self):
self.dll.Quit()
def hardware_child_process(
commands,
other_init_args):
hw = Hardware(other_init_args)
while True:
cmd, args = commands.recv()
if cmd == 'start':
hw.blocking_command(**args)
elif cmd == 'change_settings':
hw.change_settings(**args)
elif cmd == 'quit':
break
hw.close()
class Nonblocking_Hardware:
"""
This class (hopefully) duplicates the functionality of the
Hardware class, except now Hardware.blocking_command() doesn't
block other execution.
"""
def __init__(
self,
other_init_args):
self.commands, self.child_commands = mp.Pipe()
self.child = mp.Process(
target=hardware_child_process,
args=(self.child_commands,
other_init_args))
self.child.start()
def blocking_command(self, arg_1, arg_2, arg_3):
"""
Doesn't block any more!
"""
self.commands.send(
('start',
{'arg_1': arg_1,
'arg_2': arg_2,
'arg_3': arg_3}))
def change_settings(self, arg_1, arg_2):
self.commands.send(
('change_settings',
{'arg_1': arg_1,
'arg_2': arg_2}))
def close(self):
self.commands.send(('quit', {}))
self.child.join()
return None
背景:
我使用Python控制硬件,通常通过使用ctypes调用闭源DLL进行控制。经常情况下,我想要调用DLL中的某些函数,这些函数会阻塞执行直到完成,但我不希望我的控制代码被阻塞。例如,我可能正在使用模拟输出卡将相机与照明同步。在向相机发送触发脉冲之前必须调用相机DLL的“快照”函数,但是“快照”命令会阻塞,从而防止我激活模拟输出卡。
ctypes
释放 GIL,因此使其非阻塞的标准方法是使用线程 -- Twisted 的deferToThread()
,asyncio
的run_in_executor()
或其他一些在线程池中运行函数的方式 (multiprocessing.pool.ThreadPool
,concurrent.futures.ThreadPoolExecutor
)。 - jfs