在Python中共享一个存储多个进程之间对象的字典

时间:2017-11-24 10:34:20

标签: python python-2.7 multiprocessing python-multiprocessing

我正在研究一个大型脚本,其主要目的是读取许多文件的内容并存储字典中每个元素的编号。如果字典中没有该元素,那么我们将创建一个对象的新实例,然后递增,否则只增加。由于要处理的每个文件本身都很庞大,有时我需要处理100多个文件,我想加快速度并利用Python的多处理模块。这是脚本的大部分简化版本(我用......隐藏了路径,它不是真正的路径):

import multiprocessing as mp
from os import listdir
from os.path import join

manager = mp.Manager()
queue = manager.Queue()
dictionary = manager.dict()

class TestClass:
    def __init__(self):
        self._number = 0

    def increment(self):
        self._number += 1

def worker(file):
    f = open(file, 'r')
    for line in f.readlines():
        if line not in dictionary:
            dictionary[line] = TestClass()

        dictionary[line].increment()

def _list_files():
    for f in listdir("..."):
        queue.put(join("...", f))

def pool():
    _list_files()
    _pool = mp.Pool(mp.cpu_count())    

    for i in range(len(queue)):
        _pool.apply(worker, args=(queue.get()))

    _pool.close()
    _pool.join()

pool()
print(dictionary)

问题是脚本崩溃并显示消息:

AttributeError: Can't get attribute 'TestClass' on <module '__main__' from '.../multiprocessing_test.py'>  

有什么方法可以让它发挥作用吗? 我不是那个创建脚本初始版本的人,我只是添加了一些功能。鉴于此,脚本的结构必须保持不变,因为重写它会花费太多时间,即TestClassworkerlist_files不能改变它们的结构(除了所有的东西)连接多处理)

1 个答案:

答案 0 :(得分:2)

(好像你之前发过这个问题。)

您的示例代码由于多种原因而无法使用,尤其是...只是没有做任何有用的事情:

$ python tst.py
Traceback (most recent call last):
  File "tst.py", line 38, in <module>
    pool()
  File "tst.py", line 29, in pool
    _list_files()
  File "tst.py", line 25, in _list_files
    for f in listdir("..."):
OSError: [Errno 2] No such file or directory: '...'

(发布不会运行的代码并不是一个好方法,但 提供MCVE是一个好主意。)所以我修复了:

index 39014ff..1ac9f4a 100644
--- a/tst.py
+++ b/tst.py
@@ -2,6 +2,8 @@ import multiprocessing as mp
 from os import listdir
 from os.path import join

+DIRPATH = 'inputs'
+
 manager = mp.Manager()
 queue = manager.Queue()
 dictionary = manager.dict()
@@ -22,8 +24,8 @@ def worker(file):
         dictionary[line].increment()

 def _list_files():
-    for f in listdir("..."):
-        queue.put(join("...", f))
+    for f in listdir(DIRPATH):
+        queue.put(join(DIRPATH, f))

 def pool():
     _list_files()

并使用一个示例输入文件创建了一个inputs/目录:

$ ls inputs
one
$ cat inputs/one
1
one
unum

现在这个例子产生:

$ python tst.py
Traceback (most recent call last):
  File "tst.py", line 40, in <module>
    pool()
  File "tst.py", line 34, in pool
    for i in range(len(queue)):
TypeError: object of type 'AutoProxy[Queue]' has no len()

现在,我没有声称这次重写是,但我继续将其重写为 工作的内容:

import multiprocessing as mp
from os import listdir
from os.path import join

DIRPATH = 'inputs'

class TestClass:
    def __repr__(self):
        return str(self._number)

    def __init__(self):
        self._number = 0

    def increment(self):
        self._number += 1

def worker(dictionary, queue):
    while True:
        path = queue.get()
        if path is None:
            return
        f = open(path, 'r')
        for line in f.readlines():
            if line not in dictionary:
                dictionary[line] = TestClass()
            dictionary[line].increment()

def run_pool():
    manager = mp.Manager()
    queue = manager.Queue()
    dictionary = manager.dict()
    nworkers = mp.cpu_count()
    pool = mp.Pool(nworkers)

    for i in range(nworkers):
        pool.apply_async(worker, args=(dictionary, queue))

    for f in listdir(DIRPATH):
        queue.put(join(DIRPATH, f))
    for i in range(nworkers):
        queue.put(None)

    pool.close()
    pool.join()

    return dictionary

def main():
    dictionary = run_pool()
    print(dictionary)

if __name__ == '__main__':
    main()

主要区别是:

  • 我删除了所有全局变量。管理器实例,托管队列和托管字典都是run_pool的本地实例。

  • 在创建nworker工作人员之后,我将文件的名称放入队列。每个工作程序运行一个循环,读取文件名,直到它读取None名称,然后返回其(无)结果。

  • 主循环将文件名删除到队列中,以便工作人员可以在完成每个先前文件时将文件名从队列中拉出。要通知所有nworkers工作人员退出,主循环会将许多None条目添加到队列中。

  • run_pool返回最终(仍然管理的)词典。

当然,我在您的__repr__对象中添加了TestClass,以便我们可以查看计数。我还确保代码应该在Windows上运行,方法是将main驱动程序移动到一个函数中,仅在__name__ == '__main__'时调用。