python中pool+map+arcgis's py module的并行问题

学习了
《一行 Python 实现并行化 -- 日常多线程操作的新思路》http://blog.segmentfault.com/caspar/1190000000414339
原作者 Chris https://medium.com/building-things-on-the-internet/40e9b2b36148#66bf-f06f781cb52b 后,
我在用Pool多进程执行并行计算,主要是调用arcgis的py模块,发现一个问题:
我的代码:

files=['E:/select/r0_51x47.txt',
       'E:/select/r0_79x77.txt',
       'E:/select/r0_89x65.txt',
       'e:/select/r0_101x109.txt',
       'e:/select/r0_104x88.txt',
       'e:/select/r0_127x80.txt',
       'e:/select/r0_139x136.txt',
       'e:/select/r0_204x209.txt']
print ('')
print ('###################################')
pool_costtime_start= datetime.datetime.now()

pool = Pool()#len(files)

pool.map(ExtractRidge, files)

pool.close()
pool.join()

print ('Pool Cost time: '+\
        str((datetime.datetime.now() - pool_costtime_start).seconds)+\
        ' seconds')

len(files)>CPU数(我的是win7+i5),执行过程中某个进程执行到arcgis提供的函数时,会无限期等待着,如下图:
pool=Pool()情况下执行map.py
正在执行的函数
该函数体

我的解决方法是:直接设置Pool的大小,即

pool=Pool(len(files))

这样就不会出现之前的情况了,这是成功的结果
ok
同样的,我还测试了用

pool=ThreadPool() 或者
pool=ThreadPool(len(files))

实现多线程的并行计算。这个就更糟糕了,提示:
ThreadPool error
提示的函数,z_limit是第三个参数,默认值为“”

arcpy.gp.Fill_sa(dem, fill, "")

fill

但是,在指定进程数的情况下确没问题。我的问题是:

1. 对于CPU密集型的计算来说,进程数是否需要严格指定?例如我的第一个情况下,需要指定进程数才能运算成功,默认由计算机CPU个数来决定的情况就会down机;

2. 是否多线程对于CPU密集型的计算容易出错?因为我用的arcgis模块计算量都很大;

                      **请路过的大神多多指教!**
阅读 10.6k
2 个回答

先回答问题:

  1. 是的,但不是因为会 down 机。对 CPU 密集型运算来说,太多的进程意味着过多的上下文切换会浪费本可以用作计算的 CPU 资源,太少的进程可能不足以充分发挥多 CPU 的潜力。

  2. 单纯从多线程技术上来说,CPU 密集型运算并不能对多线程模型的正确性产生任何影响;但是对于设计来说,多线程模型是一个要求很高的部分,对于线程同步资源抢占等问题的不当处理很容易造成多线程程序设计中致命的性能问题,甚至影响正确性。

回到您的问题。您在使用 multiprocessing 的时候碰到的问题——由于没有您完整的代码——我推测可能有以下两点原因:

  1. 共享的资源——也就是说您在执行 pool.map() 之前创建了一些进程间共享的资源,也许您自己都没有意识到;这些资源在最终多进程执行的过程中由于某种冲突而造成了超出预期的结果。我遇到类似的例子是在 os.fork() 之前创建了一个老版本的 Twisted 的 eventloop 且注册了文件描述符,结果因为底层资源的冲突而影响了整个程序的执行速度。

  2. 没有干净的结束。multiprocessing 会把创建的进程放在一个池子里,对于不同的任务有可能会前后使用池子里的同一些进程。这里怀疑问题可能是前一个任务在进程的内存里留下了一些可能会对下一个任务有致命影响的信息。

对于您的多线程测试,对问题原因的猜测就更多了,比如代码中有一些非线程安全的全局变量,等等等等,需要根据具体任务代码来看。

作为前面几位同学讨论的补充,给出一个可运行例子以说明在multiprocessing模块的进程池中全局对象的生存周期。

foo.py

counter = dict(readed=0)
def readfile(filename):
    counter['readed']+=1
    print '%s readed %d'%(filename,counter['readed'])

main.py

import time,multiprocessing
files=['a.txt','b.txt','c.txt','d.txt','e.txt']
def dowork(filename):
    import foo
    time.sleep(2)
    foo.readfile(filename)

if __name__ == '__main__':
    pool=multiprocessing.Pool(2)
    pool.map(dowork,files)
    pool.close()
    pool.join()

python main.py输出:

a.txt readed 1
b.txt readed 1
c.txt readed 2
d.txt readed 2
e.txt readed 3

dowork被调用5次,但foo.py的全局对象counter只初始化2次。修改dowork方法为

def dowork(filename):
    from sys import modules
    import foo
    time.sleep(2)
    foo.readfile(filename)
    del modules['foo']

python main.py输出

a.txt readed 1
b.txt readed 1
c.txt readed 1
d.txt readed 1
e.txt readed 1

dowork被调用5次,foo.py的全局对象counter初始化5次,这才是我们所期待的结果。

回到题主的情况,很可能arcpy模块引用并改变某全局对象G。当进程池的进程数=文件数,全局对象G相互间无共享,运行正常;当进程池的进程数<文件数,全局对象G被共享,于是厄运开始...

引用:How to un-import modules

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏