共享内存复杂的可写数据结构

时间:2014-01-02 22:17:13

标签: python multithreading

我有一个在大图结构上运行的算法,我想制作多线程以获得更好的性能。我所看到的所有方法都不符合我的要求:我希望图形存在于共享内存中,所有进程都可以读取和写入(使用锁来防止竞争条件)。从本质上讲,我想在C中使用类似OpenMP的东西,每个线程都可以访问所有内存。

我从查看线程模块开始,但GIL意味着性能提升无关紧要。

我继续尝试多处理模块,正如我在此主题上发现的大多数帖子(例如how can I share a dictionary across multiple processes?Shared-memory objects in python multiprocessing)所建议的那样。这有两个主要问题。

首先,似乎多处理对复杂对象不起作用。考虑以下玩具问题:我有一个整数列表,并希望将它们全部乘以10,然后以任意顺序输出所有数字。我可以使用以下代码:

def multiply_list():
    manager = Manager()
    output = manager.list()
    threads = []

    for v in range(10):
        output.append(v)
    print([str(v) for v in output])

    def process(inputs, start, end):
        while start < end:
            inputs[start] *= 10
            start += 1

    t1 = Process(target=process,
        args = (output, 0, 5))
    t2 = Process(target=process,
        args = (output, 5, 10))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print([str(v) for v in output])

带输出:

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
['0', '10', '20', '30', '40', '50', '60', '70', '80', '90']

但是,如果我有一个对象列表,并修改对象:

class Container(object):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return "C" + str(self.value)

def multiply_containers():
    manager = Manager()
    output = manager.list()
    threads = []

    for v in range(10):
        output.append(Container(v))
    print([str(v) for v in output])

    def process(inputs, start, end):
        while start < end:
            inputs[start].value *= 10
            start += 1

    t1 = Process(target=process,
        args = (output, 0, 5))
    t2 = Process(target=process,
        args = (output, 5, 10))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print([str(v) for v in output])

没有变化。

['C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9']
['C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9']

另一个问题是我链接的SO帖子建议尝试写入数据结构会复制它,这是我不想要的。

为了澄清算法本身,第一步(构建图形)的工作原理如下:我有一个句子列表,它是单词序列。我想构建一个有向图,其中每个顶点都是一个单词,其外边缘会在某个句子中跟随每个单词。例如,如果我的输入是“帽子里的猫”和“房子里的猫”,我的输出图将是=&gt; cat =&gt; in =&gt; =&gt;帽子,房子(即“the”有两个外边,一个是“帽子”,一个是“房子”)。我还会跟踪一些辅助信息,例如每个句子或单词的常见程度。每个顶点都有一个入边和出边列表以及一些属性。

我发现了一个可能有效的模块(http://poshmodule.sourceforge.net/posh/html/),但我不确定是否有“规范”或推荐的方式来做这类事情。

谢谢!

1 个答案:

答案 0 :(得分:1)

下面是示例代码(可行),它使用单独的Manager进程来控制对共享数据结构的访问,并且基于您的示例代码以及问题Sharing object (class instance) in python using Managers中的问题,@ freakish说这可能是一个重复的问题在评论中 - 我不清楚它是否存在,但整体方法似乎可以解决您的问题。

from multiprocessing import Lock, Manager, Process
from multiprocessing.managers import BaseManager

class Container(object):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return "C" + str(self.value)
    def multiply(self, factor):  # added method
        self.value *= factor

def process(inputs, start, end):
    for i in range(start, end):
        inputs.apply(i, 'multiply', (10,))

class ListProxy(object):
    def __init__(self):
        self.nl = []
    def append(self, x):
        self.nl.append(x)
    def __getitem__(self, key):
        return self.nl[key]
    def __iter__(self):
        return iter(self.nl)
    def apply(self, i, method, args, **kwargs):
        getattr(self.nl[i], method)(*args, **kwargs)

class ListManager(BaseManager):
    pass

ListManager.register('ListProxy', ListProxy,
                     exposed=['append', '__getitem__', '__iter__', 'apply'])

def main():
    manager = ListManager()
    manager.start()
    output = manager.ListProxy()

    for v in range(10):
        output.append(Container(v))
    print([str(v) for v in output])

    t1 = Process(target=process, args=(output, 0, 5))
    t2 = Process(target=process, args=(output, 5, 10))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print([str(v) for v in output])

if __name__ == '__main__':
    main()