什么';这是一种制作对象的非阻塞版本的python方法吗?
我经常将python对象与阻塞直到完成的方法一起使用,并希望将这些方法转换为非阻塞版本。我发现自己经常执行以下模式:什么';这是一种制作对象的非阻塞版本的python方法吗?,python,multithreading,multiprocessing,subprocess,nonblocking,Python,Multithreading,Multiprocessing,Subprocess,Nonblocking,我经常将python对象与阻塞直到完成的方法一起使用,并希望将这些方法转换为非阻塞版本。我发现自己经常执行以下模式: 定义对象 定义一个函数,该函数创建对象的实例,并解析命令以调用对象的方法 定义一个“父”对象,该对象创建一个运行步骤2中定义的函数的子流程,并复制原始对象的方法 这就完成了任务,但涉及到大量乏味的代码重复,而且对我来说似乎不是很像Python是否有一种标准的、更好的方法来做到这一点? 一个高度简化的示例来说明我所使用的模式: import ctypes import Queue
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“snap”函数,但是“snap”命令会阻塞,阻止我激活模拟输出卡。我已经通过使用元类在对象上创建非阻塞版本的阻塞函数来做了类似的事情。只需执行以下操作,即可创建类的非阻塞版本:
class NB_Hardware(object):
__metaclass__ = NonBlockBuilder
delegate = Hardware
nb_funcs = ['blocking_command']
我采用了我最初的实现,它以Python3为目标,并使用了一个concurrent.futures.ThreadPoolExecutor
(我包装了阻塞I/O调用,使它们在asyncio
上下文*)中不阻塞),并对它们进行了调整,以使用Python2和concurrent.futures.ProcessPoolExecutor
。下面是元类及其助手类的实现:
from multiprocessing import cpu_count
from concurrent.futures import ProcessPoolExecutor
def runner(self, cb, *args, **kwargs):
return getattr(self, cb)(*args, **kwargs)
class _ExecutorMixin():
""" A Mixin that provides asynchronous functionality.
This mixin provides methods that allow a class to run
blocking methods in a ProcessPoolExecutor.
It also provides methods that attempt to keep the object
picklable despite having a non-picklable ProcessPoolExecutor
as part of its state.
"""
pool_workers = cpu_count()
def run_in_executor(self, callback, *args, **kwargs):
""" Runs a function in an Executor.
Returns a concurrent.Futures.Future
"""
if not hasattr(self, '_executor'):
self._executor = self._get_executor()
return self._executor.submit(runner, self, callback, *args, **kwargs)
def _get_executor(self):
return ProcessPoolExecutor(max_workers=self.pool_workers)
def __getattr__(self, attr):
if (self._obj and hasattr(self._obj, attr) and
not attr.startswith("__")):
return getattr(self._obj, attr)
raise AttributeError(attr)
def __getstate__(self):
self_dict = self.__dict__
self_dict['_executor'] = None
return self_dict
def __setstate__(self, state):
self.__dict__.update(state)
self._executor = self._get_executor()
class NonBlockBuilder(type):
""" Metaclass for adding non-blocking versions of methods to a class.
Expects to find the following class attributes:
nb_funcs - A list containing methods that need non-blocking wrappers
delegate - The class to wrap (add non-blocking methods to)
pool_workers - (optional) how many workers to put in the internal pool.
The metaclass inserts a mixin (_ExecutorMixin) into the inheritence
hierarchy of cls. This mixin provides methods that allow
the non-blocking wrappers to do their work.
"""
def __new__(cls, clsname, bases, dct, **kwargs):
nbfunc_list = dct.get('nb_funcs', [])
existing_nbfuncs = set()
def find_existing_nbfuncs(d):
for attr in d:
if attr.startswith("nb_"):
existing_nbfuncs.add(attr)
# Determine if any bases include the nb_funcs attribute, or
# if either this class or a base class provides an actual
# implementation for a non-blocking method.
find_existing_nbfuncs(dct)
for b in bases:
b_dct = b.__dict__
nbfunc_list.extend(b_dct.get('nb_funcs', []))
find_existing_nbfuncs(b_dct)
# Add _ExecutorMixin to bases.
if _ExecutorMixin not in bases:
bases += (_ExecutorMixin,)
# Add non-blocking funcs to dct, but only if a definition
# is not already provided by dct or one of our bases.
for func in nbfunc_list:
nb_name = 'nb_{}'.format(func)
if nb_name not in existing_nbfuncs:
dct[nb_name] = cls.nbfunc_maker(func)
return super(NonBlockBuilder, cls).__new__(cls, clsname, bases, dct)
def __init__(cls, name, bases, dct):
""" Properly initialize a non-blocking wrapper.
Sets pool_workers and delegate on the class, and also
adds an __init__ method to it that instantiates the
delegate with the proper context.
"""
super(NonBlockBuilder, cls).__init__(name, bases, dct)
pool_workers = dct.get('pool_workers')
delegate = dct.get('delegate')
old_init = dct.get('__init__')
# Search bases for values we care about, if we didn't
# find them on the child class.
for b in bases:
if b is object: # Skip object
continue
b_dct = b.__dict__
if not pool_workers:
pool_workers = b_dct.get('pool_workers')
if not delegate:
delegate = b_dct.get('delegate')
if not old_init:
old_init = b_dct.get('__init__')
cls.delegate = delegate
# If we found a value for pool_workers, set it. If not,
# ExecutorMixin sets a default that will be used.
if pool_workers:
cls.pool_workers = pool_workers
# Here's the __init__ we want every wrapper class to use.
# It just instantiates the delegate object.
def init_func(self, *args, **kwargs):
# Be sure to call the original __init__, if there
# was one.
if old_init:
old_init(self, *args, **kwargs)
if self.delegate:
self._obj = self.delegate(*args, **kwargs)
cls.__init__ = init_func
@staticmethod
def nbfunc_maker(func):
def nb_func(self, *args, **kwargs):
return self.run_in_executor(func, *args, **kwargs)
return nb_func
class Hardware:
def __init__(self, stuff):
self.stuff = stuff
return
def blocking_command(self, arg1):
self.stuff.call_function(arg1)
return
用法:
from nb_helper import NonBlockBuilder
import time
class Hardware:
def __init__(self, other_init_args):
self.other = other_init_args
def blocking_command(self, arg_1, arg_2, arg_3):
print("start blocking")
time.sleep(5)
return "blocking"
def normal_command(self):
return "normal"
class NBHardware(object):
__metaclass__ = NonBlockBuilder
delegate = Hardware
nb_funcs = ['blocking_command']
if __name__ == "__main__":
h = NBHardware("abc")
print "doing blocking call"
print h.blocking_command(1,2,3)
print "done"
print "doing non-block call"
x = h.nb_blocking_command(1,2,3) # This is non-blocking and returns concurrent.future.Future
print h.normal_command() # You can still use the normal functions, too.
print x.result() # Waits for the result from the Future
输出:
doing blocking call
start blocking
< 5 second delay >
blocking
done
doing non-block call
start blocking
normal
< 5 second delay >
blocking
如果您不需要这种灵活性,您可以大大简化实现:
class NonBlockBuilder(type):
""" Metaclass for adding non-blocking versions of methods to a class.
Expects to find the following class attributes:
nb_funcs - A list containing methods that need non-blocking wrappers
delegate - The class to wrap (add non-blocking methods to)
pool_workers - (optional) how many workers to put in the internal pool.
The metaclass inserts a mixin (_ExecutorMixin) into the inheritence
hierarchy of cls. This mixin provides methods that allow
the non-blocking wrappers to do their work.
"""
def __new__(cls, clsname, bases, dct, **kwargs):
nbfunc_list = dct.get('nb_funcs', [])
# Add _ExecutorMixin to bases.
if _ExecutorMixin not in bases:
bases += (_ExecutorMixin,)
# Add non-blocking funcs to dct, but only if a definition
# is not already provided by dct or one of our bases.
for func in nbfunc_list:
nb_name = 'nb_{}'.format(func)
dct[nb_name] = cls.nbfunc_maker(func)
return super(NonBlockBuilder, cls).__new__(cls, clsname, bases, dct)
def __init__(cls, name, bases, dct):
""" Properly initialize a non-blocking wrapper.
Sets pool_workers and delegate on the class, and also
adds an __init__ method to it that instantiates the
delegate with the proper context.
"""
super(NonBlockBuilder, cls).__init__(name, bases, dct)
pool_workers = dct.get('pool_workers')
cls.delegate = dct['delegate']
# If we found a value for pool_workers, set it. If not,
# ExecutorMixin sets a default that will be used.
if pool_workers:
cls.pool_workers = pool_workers
# Here's the __init__ we want every wrapper class to use.
# It just instantiates the delegate object.
def init_func(self, *args, **kwargs):
self._obj = self.delegate(*args, **kwargs)
cls.__init__ = init_func
@staticmethod
def nbfunc_maker(func):
def nb_func(self, *args, **kwargs):
return self.run_in_executor(func, *args, **kwargs)
return nb_func
*原始代码仅供参考。我用来异步启动类方法的一种方法是创建一个池并使用调用几个函数别名,而不是直接调用类方法 假设您有一个更简单的类版本:
from multiprocessing import cpu_count
from concurrent.futures import ProcessPoolExecutor
def runner(self, cb, *args, **kwargs):
return getattr(self, cb)(*args, **kwargs)
class _ExecutorMixin():
""" A Mixin that provides asynchronous functionality.
This mixin provides methods that allow a class to run
blocking methods in a ProcessPoolExecutor.
It also provides methods that attempt to keep the object
picklable despite having a non-picklable ProcessPoolExecutor
as part of its state.
"""
pool_workers = cpu_count()
def run_in_executor(self, callback, *args, **kwargs):
""" Runs a function in an Executor.
Returns a concurrent.Futures.Future
"""
if not hasattr(self, '_executor'):
self._executor = self._get_executor()
return self._executor.submit(runner, self, callback, *args, **kwargs)
def _get_executor(self):
return ProcessPoolExecutor(max_workers=self.pool_workers)
def __getattr__(self, attr):
if (self._obj and hasattr(self._obj, attr) and
not attr.startswith("__")):
return getattr(self._obj, attr)
raise AttributeError(attr)
def __getstate__(self):
self_dict = self.__dict__
self_dict['_executor'] = None
return self_dict
def __setstate__(self, state):
self.__dict__.update(state)
self._executor = self._get_executor()
class NonBlockBuilder(type):
""" Metaclass for adding non-blocking versions of methods to a class.
Expects to find the following class attributes:
nb_funcs - A list containing methods that need non-blocking wrappers
delegate - The class to wrap (add non-blocking methods to)
pool_workers - (optional) how many workers to put in the internal pool.
The metaclass inserts a mixin (_ExecutorMixin) into the inheritence
hierarchy of cls. This mixin provides methods that allow
the non-blocking wrappers to do their work.
"""
def __new__(cls, clsname, bases, dct, **kwargs):
nbfunc_list = dct.get('nb_funcs', [])
existing_nbfuncs = set()
def find_existing_nbfuncs(d):
for attr in d:
if attr.startswith("nb_"):
existing_nbfuncs.add(attr)
# Determine if any bases include the nb_funcs attribute, or
# if either this class or a base class provides an actual
# implementation for a non-blocking method.
find_existing_nbfuncs(dct)
for b in bases:
b_dct = b.__dict__
nbfunc_list.extend(b_dct.get('nb_funcs', []))
find_existing_nbfuncs(b_dct)
# Add _ExecutorMixin to bases.
if _ExecutorMixin not in bases:
bases += (_ExecutorMixin,)
# Add non-blocking funcs to dct, but only if a definition
# is not already provided by dct or one of our bases.
for func in nbfunc_list:
nb_name = 'nb_{}'.format(func)
if nb_name not in existing_nbfuncs:
dct[nb_name] = cls.nbfunc_maker(func)
return super(NonBlockBuilder, cls).__new__(cls, clsname, bases, dct)
def __init__(cls, name, bases, dct):
""" Properly initialize a non-blocking wrapper.
Sets pool_workers and delegate on the class, and also
adds an __init__ method to it that instantiates the
delegate with the proper context.
"""
super(NonBlockBuilder, cls).__init__(name, bases, dct)
pool_workers = dct.get('pool_workers')
delegate = dct.get('delegate')
old_init = dct.get('__init__')
# Search bases for values we care about, if we didn't
# find them on the child class.
for b in bases:
if b is object: # Skip object
continue
b_dct = b.__dict__
if not pool_workers:
pool_workers = b_dct.get('pool_workers')
if not delegate:
delegate = b_dct.get('delegate')
if not old_init:
old_init = b_dct.get('__init__')
cls.delegate = delegate
# If we found a value for pool_workers, set it. If not,
# ExecutorMixin sets a default that will be used.
if pool_workers:
cls.pool_workers = pool_workers
# Here's the __init__ we want every wrapper class to use.
# It just instantiates the delegate object.
def init_func(self, *args, **kwargs):
# Be sure to call the original __init__, if there
# was one.
if old_init:
old_init(self, *args, **kwargs)
if self.delegate:
self._obj = self.delegate(*args, **kwargs)
cls.__init__ = init_func
@staticmethod
def nbfunc_maker(func):
def nb_func(self, *args, **kwargs):
return self.run_in_executor(func, *args, **kwargs)
return nb_func
class Hardware:
def __init__(self, stuff):
self.stuff = stuff
return
def blocking_command(self, arg1):
self.stuff.call_function(arg1)
return
在模块的顶层,定义如下所示的新函数:
def _blocking_command(Hardware_obj, arg1):
return Hardware_obj.blocking_command(Hardware_obj, arg1)
由于类和这个“别名”函数都是在模块的顶层定义的,因此它们是可pickle的,您可以使用多处理库启动它:
import multiprocessing
hw_obj = Harware(stuff)
pool = multiprocessing.Pool()
results_obj = pool.apply_async(_blocking_command, (hw_obj, arg1))
函数调用的结果将在results对象中可用。我喜欢这种方法,因为它使用相对较少的代码来简化并行化。具体来说,它只添加了一些两行函数而不是任何类,并且除了多处理之外,不需要额外的导入
注:
ctypes
释放了GIL,因此使其非阻塞的标准方法是线程——TwisteddeferToThread()
,asyncio
run\u in\u executor()
或其他在线程池中运行函数的方法(multiprocessing.pool.ThreadPool
,concurrent.futures.ThreadPoolExecutor
)。