Python使用multiprocessing多进程报错的一种情况解决方法

本文探讨了Python使用multiprocessing进行多进程执行时遇到的问题,包括权限冲突、网络访问超限等问题,并提出了通过保护进程创建过程来避免无限递归的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题

Python使用multiprocessing进行多进程执行是常常会报各种奇怪的错误。比如使用tushare时会反复拉起pro_api,全局变量访问次数超过限制,进程帧读取权限报错等等。

分析

注意到一个问题,在这种情况下无论是报什么错误,往往是因为权限冲突或者反复调用。这意味着很多不应该被重复调用的工具被多次重复调用了。

在multiprocessing的官方文档中,我找到了这样一条提示:

Functionality within this package requires that the __main__ module be importable by the children. This is covered in Programming guidelines however it is worth pointing out here. This means that some examples, such as the multiprocessing.pool.Pool examples will not work in the interactive interpreter.

也就是说,在交互式环境中,我们应当使用main保护进程创建过程。但是实际上在验证时,未必有效。

在Stack Overflow中,我找到了这样一条说明:

On some systems, multiprocessing has to spawn a new copy of python and import your module to get to the worker code. Anything at module level is executed again... including the parent code that creates the pool. This would be an infinite recursion except python detects the problem and gives you a handy tip.

 也就是说,这是因为multiprocessing会复制整个Python进程环境来保证子进程能够工作,然后重新执行所有模块级的任务,如果创建进程的过程不受保护,就会形成无限递归。

解决方法 

综合上述两点,做出一个基本猜测:由于multiprocessing会复制整个父进程来保证子进程能够工作,那么创建进程的过程必须受到main的保护以避免子进程的进程池创建过程调用创建进程池的方法。那么在任何时候保证只在main空间能创建进程池即可。代码如下:

if __name__ == "__main__":
    with mp.Pool(10) as pool:
        use_function(pool, *args, **kwargs)

评价

这里往往报错的不会是multiprocessing,而且往往不止一个地方报错,因此会比较有迷惑性。我这次,同时出现了读取权限冲突、网络访问超限、程序拉起出错等错误。上在出现多重错误的时候就应该能想到可能问题不出在所有执行过程了,所以我首先检查了multiprocessing,但还是错过了,后来排查了一圈回来还是multiprocessing的嫌疑最大,只有这个模块我使用的懒人方法,里头的创建过程对我而言是完全黑箱的。

<think>好的,我现在需要解决用户的问题:在Pythonmultiprocessing模块中,是否可以将另一个类的方法作为Process的target参数,以及如何实现这一点。用户还希望看到示例代码。 首先,我需要回忆Python多进程的基本知识。multiprocessing模块的Process类确实需要指定一个target函数,作为新进程的入口点。通常,这个target是一个函数,但用户想知道是否可以使用类的方法。 接下来,考虑类的方法是否可以作为target。在Python中,类的方法在调用时需要绑定到实例上,也就是说,方法需要访问self。如果直接传递instance.method给target,可能会有序列化的问题,或者作用域的问题,特别是在Windows系统上,因为multiprocessing在Windows下使用spawn启动方法,需要能够序列化目标函数及其参数。 可能的解决方案包括: 1. 将类的方法转换为静态方法或类方法,这样它们不需要实例即可调用。但这样可能无法访问实例属性,限制了灵活性。 2. 使用lambda或闭包来绑定实例和方法。例如,传递target=lambda: instance.method()。但需要注意实例是否在子进程中正确序列化。 3. 使用functools.partial来绑定实例和方法,这可能更可靠一些。 4. 在类内部定义__call__方法,让实例自身成为可调用的对象,这样可以直接将实例作为target传递。 5. 使用队列或管道在进程间传递数据,但可能复杂。 接下来,我需要验证这些方法的可行性。比如,使用实例方法直接作为target是否可行。在测试中,可能发现直接传递instance.method会导致PicklingError,因为实例方法无法被正确序列化。因此,必须找到其他方法。 例如,使用静态方法或类方法,这样不需要绑定实例。但用户可能需要访问实例的状态,所以静态方法可能不够。 使用lambda或partial可能有效,但需要确保实例被正确传递。例如: import multiprocessing class MyClass: def method(self): print("Called method") if __name__ == '__main__': obj = MyClass() p = multiprocessing.Process(target=lambda: obj.method()) p.start() p.join() 但这里可能存在问题,尤其是在Windows中,因为obj需要被pickle。如果obj包含无法pickle的属性,就会出错。因此,可能需要确保实例是可序列化的。 另一种方法使用partial: from functools import partial p = multiprocessing.Process(target=partial(MyClass.method, obj)) 这可能更可靠,因为partial将方法和实例绑定在一起,但同样需要实例可序列化。 或者,在类中定义__call__方法: class MyCallableClass: def __call__(self): print("Called __call__") if __name__ == '__main__': obj = MyCallableClass() p = multiprocessing.Process(target=obj) p.start() p.join() 这里,实例obj本身是可调用的,所以可以作为target。但同样需要确保obj可被pickle。 此外,还可以在子类中覆盖run方法,创建自定义的Process类,这样不需要传递target,而是直接调用start()时执行run方法。这可能更适合用户的需求,但用户的问题是关于使用其他类的方法作为target,所以可能需要调整示例。 总结可能的实现方式: 1. 使用实例方法,结合lambda或partial传递实例。 2. 使用可调用的类实例(实现__call__方法)。 3. 将方法定义为静态或类方法,避免绑定实例。 4. 使用队列或共享内存传递数据,但可能增加复杂度。 需要测试这些方法,特别是跨平台的兼容性。例如,在Windows下,由于spawn启动方式,所有传递给子进程的参数必须可被pickle。因此,如果实例中有无法pickle的属性,如文件句柄或数据库连接,就会失败。 在示例代码中,应该展示可行的几种方式,并说明注意事项,比如确保实例可序列化,或者使用不同的方法绑定。 可能的示例代码包括: - 使用lambda绑定实例和方法。 - 使用partial。 - 使用__call__方法。 - 使用静态方法。 最后,需要提醒用户关于跨平台的问题,以及在类方法中访问共享资源时的同步机制,比如使用Lock等。</think>在Python的`multiprocessing`模块中,可以将其他类的方法作为`Process`的`target`参数,但需注意方法的绑定和序列化问题。以下是实现方式与示例: --- ### **实现方式** 1. **通过实例绑定方法** 使用`lambda`或`functools.partial`将类实例与目标方法绑定,确保子进程能正确调用。 2. **定义可调用类** 在类中实现`__call__`方法,使实例自身成为可调用对象。 3. **静态方法或类方法** 若方法无需实例状态,可将其定义为`@staticmethod`或`@classmethod`,直接作为`target`。 --- ### **示例代码** #### **1. 使用lambda绑定实例方法** ```python import multiprocessing from functools import partial class MyClass: def my_method(self): print("子进程执行类方法") if __name__ == '__main__': obj = MyClass() # 方式1: lambda绑定 process1 = multiprocessing.Process(target=lambda: obj.my_method()) # 方式2: partial绑定 process2 = multiprocessing.Process(target=partial(obj.my_method)) process1.start() process2.start() process1.join() process2.join() ``` #### **2. 可调用类(实现__call__方法)** ```python class CallableClass: def __call__(self): print("子进程通过__call__执行") if __name__ == '__main__': obj = CallableClass() process = multiprocessing.Process(target=obj) process.start() process.join() ``` #### **3. 静态方法** ```python class MyStaticClass: @staticmethod def static_method(): print("子进程执行静态方法") if __name__ == '__main__': process = multiprocessing.Process(target=MyStaticClass.static_method) process.start() process.join() ``` --- ### **注意事项** 1. **序列化限制** 若类实例包含无法序列化的属性(如文件句柄、网络连接),在Windows下会因`pickle`失败报错。需确保实例数据简单可序列化[^1]。 2. **跨平台兼容性** 在Linux/macOS中使用`fork`创建子进程时,实例状态可直接共享;而Windows使用`spawn`,需重新初始化对象。 3. **避免共享状态竞争** 多进程间共享数据时,需通过`Queue`或`Manager`同步,避免竞争条件。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值