Python多线程多进程中的几个坑

Introduction

本文系转载,原文: 今天遇到的Python多线程、多进程中的几个坑

今天在写oj的判题端的时候犯了一个低级错误,就是为了加快判题速度,我就采用了多线程多组用例同时运行的方法,但是后来不经意的发现,明明跑的很快的程序到了我这实际运行时间就变成了好几倍,而cpu时间并没有太大的变化。

我开始怀疑是runner的问题,因为以前使用ptrace的runner的时候,ptrace会在进程用户态和内核态之间反复的检查,导致程序运行缓慢。但是我手动的使用命令行启动runner运行的时候,发现并没有问题,cpu时间和实际运行时间几乎一样的。我就开始怀疑是我的Python代码的问题,后来恍然大悟,为了能让cpu时间更长一些,方便测试,我写了一个时间复杂度很高的c程序,这是在运行一个cpu密集型的应用啊,而由于GIL的存在,Python多线程并不适合干这个,这个场景应该使用多进程。更详细的解释参考 http://www.oschina.net/translate/pythons-hardest-problem

下面是我的测试数据,

//多线程
//两组用例同时运行
{‘cpu_time’: 3543.0, ‘real_time’: 13384.0, ‘test_case_id’: 2}
{‘cpu_time’: 3592.0, ‘real_time’: 13688.0, ‘test_case_id’: 1}

//只有一组测试用例
{‘cpu_time’: 3612.0, ‘real_time’: 6856.0, ‘test_case_id’: 1}
很明显的结果,下面是采用了多进程之后的测试数据

//多进程
//两组用例同时运行
{‘cpu_time’: 4110.0, ‘real_time’: 4250.0, ‘test_case_id’: 2}
{‘cpu_time’: 4121.0, ‘real_time’: 4298.0, ‘test_case_id’: 1}

//一组用例
{‘cpu_time’: 3861.0, ‘real_time’: 4040.0, ‘test_case_id’: 1}
好了,其实我不是专门想说这个的了,因为这是一个愚蠢的问题。我要记录一下今天遇到的三个多进程中的问题:

第一个问题

PicklingError: Can’t pickle : attribute lookup builtin.instancemethod failed

poc如下

from multiprocessing import Pool

class Runner(object):
def func(self, i):
print i
return i

runner = Runner()
pool = Pool(processes=5)
for i in range(5):
pool.apply_async(runner.func, (i, ))
pool.close()
pool.join()
这个问题只出现在Python2上,Python3没有问题。这是因为多进程之间要使用pickle来序列化并传递一些数据,但是实例方法并不能被pickle,参见 Python文档,可以被pickle的类型列表
,还有在Python3中实例方法可以被pickle了,见 Python bug list

最简单的解决办法就是写一个可以被pickle的函数代理一下

from multiprocessing import Pool

def run(cls_instance, i):
return cls_instance.func(i)

class Runner(object):
def func(self, i):
print i
return i

runner = Runner()
pool = Pool(processes=5)
for i in range(5):
pool.apply_async(run, (runner, i))
pool.close()
pool.join()
还有一个方法
已经被指出可能存在缺陷了,就是这个人第一个例子,但是我不知道为什么一个类可以被析构多次呢?是不是这个类实例化一次以后就被复制到了各个进程上,然后再单独进行的析构呢。这个人第二个例子是反驳的 call
方法的,我没法运行,总是提示 Can’t pickle : attribute lookup builtin.instancemethod failed
,估计是一样的原因。

第二个问题

pool作为实例变量的时候出错 pool objects cannot be passed between processes or pickled

把上面的例子稍微的改造了一下,

from multiprocessing import Pool

def run(cls_instance, i):
return cls_instance.func(i)

class Runner(object):
def init(self):
pool = Pool(processes=5)
for i in range(5):
pool.apply_async(run, (self, i))
pool.close()
pool.join()

def func(self, i):
    print i
    return i

runner = Runner()
把pool放在实例内部了,使用外部函数代理,运行正常。但是如果把里面的pool都换成 self.pool
的话,就会出现上面的错误。原因是在pickle传递给pool的对象的时候,这个对象就包含pool这个实例变量,它不能被pickle,造成错误。而不使用self的话,那就是 init
方法的一个局部变量,不受影响。解决方法就是自己实现 getstate
方法,它是决定什么需要pickle的函数,我们删除掉pool,不让它pickle就好了。 setstate
作用是相反的,是用来增加实例变量的。

看例子

from multiprocessing import Pool

def run(cls_instance, i):
return cls_instance.func(i)

class Runner(object):
def init(self):
self.pool = Pool(processes=2)
self.var = 10
for i in range(2):
self.pool.apply_async(run, (self, i))
self.pool.close()
self.pool.join()

def func(self, i):
    print i
    return i

def __getstate__(self):
    self_dict = self.__dict__.copy()
    print self.__dict__
    del self_dict['pool']
    return self_dict

def __setstate__(self, state):
    print state
    self.__dict__.update(state)

runner = Runner()
输出是

{‘var’: 10, ‘pool’: }
{‘var’: 10, ‘pool’: }
{‘var’: 10}
0
{‘var’: 10}
1
也就是实例在unpickle的时候丢了pool这个变量,但是我们也不需要了,所以可以这样解决问题。

第三个问题

子进程引发的异常怎么消失了?

from multiprocessing import Pool

def run(cls_instance, i):
return cls_instance.func(i)

class Runner(object):
def init(self):
self.pool = Pool(processes=2)
self.var = 10
for i in range(2):
self.pool.apply_async(run, (self, i))
self.pool.close()
self.pool.join()

def func(self, i):
    if i == 1:
        raise ValueError("xxx")
    print i
    return i

def __getstate__(self):
    self_dict = self.__dict__.copy()
    del self_dict['pool']
    return self_dict

def __setstate__(self, state):
    self.__dict__.update(state)

runner = Runner()
只能打印出一个0来,当i为1的时候有一个异常啊,怎么没显示出来。在 文档
中这么说的

get([timeout]) Return the result when it arrives. If timeout is not None and the result does not arrive within timeout seconds then multiprocessing.TimeoutError is raised. If the remote call raised an exception then that exception will be reraised by get().
apply_async
返回的是 AsyncResult
,其中出现的异常只有在调用AsyncResult.get()的时候才会被重新引发。

from multiprocessing import Pool

def run(cls_instance, i):
return cls_instance.func(i)

class Runner(object):
def init(self):
self.pool = Pool(processes=2)
results = []
for i in range(2):
results.append(self.pool.apply_async(run, (self, i)))
self.pool.close()
self.pool.join()
for i in range(2):
print results[i].get()

def func(self, i):
    if i == 1:
        raise ValueError("xxx")
    return i

def __getstate__(self):
    self_dict = self.__dict__.copy()
    del self_dict['pool']
    return self_dict

def __setstate__(self, state):
    self.__dict__.update(state)

runner = Runner()

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当涉及到多线程编程时,确实存在一些点。以下是几个常见的 Python 多线程: 1. 全局解释器锁(GIL):在 Python ,由于 GIL 的存在,一个线程只能在一个时间点上执行 Python 代码。这意味着即使你有多个线程,你的代码也无法并行执行。因此,如果你想要充分利用多核 CPU,你需要使用多个进程而不是多个线程。 2. 竞争条件:多个线程同时访问共享资源可能会导致竞争条件。例如,如果两个线程同时尝试向同一个文件写入数据,可能会导致数据损坏或丢失。为了避免这种情况,你需要使用锁或其他同步原语来确保每个线程访问资源的顺序。 3. 内存泄漏:多线程应用程序的内存泄漏可能比单线程程序更难以跟踪和调试。因为多个线程可以同时访问相同的内存地址,所以即使一个线程已经释放了一块内存,其他线程仍然可以访问它。为了避免这种情况,你需要确保在不再需要内存时正确地释放它。 4. 连接池的:当使用连接池时,你需要小心不要在多个线程之间共享连接。如果你的应用程序使用了多个线程,并且每个线程都尝试从连接池获取连接,则可能会出现竞争条件。为了避免这种情况,你需要为每个线程使用独立的连接池。 总之,在使用 Python 进行多线程编程时,你需要小心处理这些点,并且确保你的应用程序在多线程环境下稳定和可靠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值