通过列表中的项进行多线程处理(Python)

时间:2014-01-07 21:43:55

标签: python multithreading list

目前我正在使用Python编写启发式问题脚本,并且我正在努力使脚本尽可能快。该脚本正在运行,现在是优化它的挑战。我想使用多线程(或多处理)来完成我的整个代码的一小部分,这里是我正在谈论的代码片段:

valid_children = set()
for focus_genome in a_list_of_genomes: # a list of objects
    [valid_children.update(set([child])) for child in \ 
    focus_genome.children() if not child in all_genomes]

基本相同,更具可读性:

valid_children = set()
for focus_genome in a_list_of_genomes: # a list of objects
    children = focus_genome.children()
    for child in children: # a list of objects (again)
        if child in all_genomes:
            valid_children.update(set([child]))

all_genomes是一个包含所有已发现基因组的列表。

我尝试了以下操作,但它没有更新集:

def threaded_function(focus_genome):
    # same as above
    [valid_children.update(set([child])) for child in focus_genome.children() \
    if not child in all_genomes]

def main_function():
    ...
    do things
    ...
    thread = Thread(target = threaded_function, args = (focus_genome,) )
    thread.start()
    thread.join()

谁能帮我回到路上?提前谢谢!

1 个答案:

答案 0 :(得分:2)

您的代码存在一些问题......但不是您要问的问题。

由于你没有提供足够的东西来运行任何东西,我添加了这些额外的东西:

class Genome(object):
    i = 0
    def __init__(self, newi = None):
        if newi is None:
            newi = Genome.i
            Genome.i += 1
        self.i = newi
    def __repr__(self):
        return 'Genome({})'.format(self.i)
    def children(self):
        return self._children

g1, g2 = Genome(), Genome()
g1._children = [Genome(), Genome()]
g2._children = [Genome(), Genome(), Genome()]
a_list_of_genomes = [g1, g2]
all_genomes = [g1.children()[0], g2.children()[2]]

现在,你的算法应该给我们基因组#2和#6。那么,让我们试试你的非线程代码:

valid_children = set()
for focus_genome in a_list_of_genomes: # a list of objects
    for child in focus_genome.children(): # a list of objects (again)
        if child in all_genomes:
            valid_children.update(set([child]))
print(valid_children)

我得到{Genome(2), Genome(6)},这是正确的。

现在,您的线程代码。复制并粘贴内部循环体作为函数体,以确保它是相同的:

def threaded_function(focus_genome):
    for child in focus_genome.children(): # a list of objects (again)
        if child in all_genomes:
            valid_children.update(set([child]))

按照您的尝试运行:

for focus_genome in a_list_of_genomes: # a list of objects
    t = threading.Thread(target=threaded_function, args=(focus_genome,))
    t.start()
    t.join()

print(valid_children)

我得到{Genome(2), Genome(6)},这不是你所声称的空,并且是正确的,与非线程版本完全相同。


话虽如此,你实际上并没有在这里做任何有用的 - 如果你这样做了,你就会遇到问题。

首先,join坐在那里等待后台线程完成。因此,启动一个线程并立即加入它没有任何好处。相反,你需要启动一堆线程,然后加入所有线程。例如:

threads = [threading.Thread(target=threaded_function, args=(focus_genome,))
           for focus_genome in a_list_of_genomes]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

但是如果线程除了运行CPU密集型Python代码之外什么都不做,那么无论如何这都无济于事,因为Global Interpreter Lock确保一次只能有一个线程运行Python代码。当您花费所有时间进行I / O(读取文件或HTTP URL),等待用户交互或在NumPy等少数库中调用慢速函数时,线程非常棒,这些库是专为线程设计的。但是为了并行运行Python代码,它们根本不会加快速度。为此,您需要流程。

与此同时,你有多个线程试图改变共享对象,没有任何同步。这是一种竞争条件,会导致数据损坏。如果要使用线程,则需要使用锁或其他同步对象来保护共享数据:

valid_children_lock = Lock()

def threaded_function(focus_genome):
    for child in focus_genome.children(): # a list of objects (again)
        if child in all_genomes:
            with valid_children_lock():
                valid_children.update(set([child]))

当您使用进程时,这种可变共享数据线程会变得更糟。如果您尝试直接在两个进程之间共享一个集合,那么有时在Unix上运行,而从不在Windows上运行。

如果您可以重新组织逻辑以不使用可变共享数据,那么一切都变得容易多了。一个非常简单的方法是根据带有参数和返回值的任务来编写所有内容 - 即没有副作用的函数。然后,您可以使用线程池或执行程序来运行所有这些任务并返回结果。这样做的另一个好处是,您可以在工作人员的同时运行任意数量的任务,自动排队其余任务,而不是尝试一次性运行所有这些任务(这要快得多)。

我们可以在这做吗?也许。如果每个任务都返回为给定valid_children找到的一组focus_genome,那么我们可以union将所有这些集合放在一起并获得完整的结果,对吧?所以:

def threaded_task(focus_genome):
    valid_children = set()
    for child in focus_genome.children(): # a list of objects (again)
        if child in all_genomes:
            valid_children.add(child)
    return valid_children

valid_children = set()
with multiprocessing.Pool() as pool:
    for subset in pool.imap_unordered(threaded_task, a_list_of_genomes):
        valid_children.update(subset)

我们可以通过使用一些理解来进一步简化这一点:

def threaded_task(focus_genome):
    return {child for child in focus_genome.children() if child in all_genomes}
with multiprocessing.Pool() as pool:
    valid_children = set.union(pool.imap_unordered(threaded_task, a_list_of_genomes))