Python Concurrent.futures使用指南-一个同时使用线程和处理的简单示例

Python Concurrent.futures使用指南-一个同时使用线程和处理的简单示例,python,multithreading,python-3.x,Python,Multithreading,Python 3.x,我想使用模块启用程序的并行处理/线程 不幸的是,我似乎找不到任何使用concurrent.futures模块的好的、简单的、防白痴的例子。它们通常需要更高级的python知识或处理/线程概念和术语 下面是基于我的程序的一个简化的、自包含的示例:有一个纯CPU绑定的任务,非常适合多处理器处理,还有一个单独的IO绑定的任务插入到数据库(SQLite)中。 在我的程序中,我已经将其转换为使用多处理池类,但是由于CPU绑定任务的结果都是在等待任务完成时收集的,因此它使用了大量内存。 因此,我希望使用线程

我想使用模块启用程序的并行处理/线程

不幸的是,我似乎找不到任何使用concurrent.futures模块的好的、简单的、防白痴的例子。它们通常需要更高级的python知识或处理/线程概念和术语

下面是基于我的程序的一个简化的、自包含的示例:有一个纯CPU绑定的任务,非常适合多处理器处理,还有一个单独的IO绑定的任务插入到数据库(SQLite)中。 在我的程序中,我已经将其转换为使用多处理池类,但是由于CPU绑定任务的结果都是在等待任务完成时收集的,因此它使用了大量内存。 因此,我希望使用线程/处理的组合,我相信concurrent.futures可以非常简单地为我实现

那么,如何将下面的内容转换为使用此模块的内容呢

import sqlite3

#Stand in CPU intensive task
def calculate(value):
    return value * 10

#Stand in Thread I/O intensive task
def output(value):
    global db

    if (value % 1000) == 0:
        db.execute('delete from test_table')

    db.execute('insert into test_table (result) values (?)', (value,))

def main():
    global db
    results = []

    db  = sqlite3.connect('e:\\z_dev\\test.sqlite')
    db.cursor()

    #=========
    #Perform CPU intensive task
    for i in range(1000):
        results.append( calculate(i))

    #Perform Threading intensive task
    for a in results:
        output(a)
    #=========

    db.commit()
    db.close()

if __name__ == '__main__':
    main()
我正在寻找一个不使用任何花哨/复杂python的答案。或者是一个清晰简单的解释,或者两者兼而有之

谢谢

编辑:我当前的“多处理器”实现。可能是错的,但似乎有效。没有任何线程。这在上面的“#==========”部分中

#Multiprocessing
pool = multiprocessing.Pool(None)
for i in range(1000):
    results.append( pool.apply_async(calculate(i)))
pool.close()
pool.join()

for i in results:
    results[i] = results[i].get()

#Complete lack of threading; but if I had it, it'd be here:     
for a in results:
    output(a)

concurrent.futures
具有最低限度的API。它很容易用于非常简单的问题,但是您没有非常简单的问题。如果你这样做了,你就已经解决了;-)

您没有显示您编写的任何
多处理.Pool
代码,但这将是一个更有希望的起点-假设您更想解决问题,而不是确认您的希望,如果您只切换到较弱的API,那么这一定很容易做到;-)

使用
多处理
的“明显”方法是使用
池。应用\u async()
方法,将异步结果对象放在有界的
队列上。队列
,让主程序中的线程将其从
队列
中拉出,然后等待结果显示。这很容易,但不是魔术。它解决了您的问题,因为有界
队列
在以不同速度运行的生产者和消费者之间进行调解的标准方式。在concurrent.futures中没有任何东西可以直接解决这个问题,它是“海量内存”问题的核心

这是你需要保持生产者和消费者大致平衡的事情,在任何标准库中都没有任何东西能神奇地为你做到这一点

编辑-充实它

这是一个完整的可执行示例,任何使用Python3的人都可以运行。注:

  • 它不使用您的代码片段,因为这些代码片段依赖于外部数据库模块,并非每个人都可以运行
  • 它坚持使用
    concurrent.futures
    来管理进程和线程。使用
    多处理
    线程
    并不是很难,实际上,这里使用线程的方式是,直接使用
    线程
    会更容易一些。但这条路很清楚
  • concurrent.futures
    Future
    对象与
    多处理
    异步结果对象基本相同-API功能的拼写不同
  • 您的问题并不简单,因为它有多个可以以不同速度运行的阶段。同样,任何标准库中的任何东西都无法通过魔法隐藏其潜在的不良后果。创建自己的有界队列仍然是最好的解决方案。对于
    MAX\u QUEUE\u SIZE
    的任何正常值,此处的内存使用将保持适度
  • 通常,您不希望创建的CPU绑定工作进程多于一个少于可用内核数的工作进程。主程序也需要周期来运行,操作系统也是如此
  • 一旦你习惯了这些东西,这段代码中的所有注释都会很烦人,比如在代码行
    i+=1
    ;-)上看到注释“增量1”)
代码如下:

import concurrent.futures as cf
import threading
import queue

NUM_CPUS = 3
NUM_THREADS = 4
MAX_QUEUE_SIZE = 20

# Runs in worker processes.
def producer(i):
    return i + 10

def consumer(i):
    global total
    # We need to protect this with a lock because
    # multiple threads in the main program can
    # execute this function simultaneously.
    with sumlock:
        total += i

# Runs in threads in main program.
def consume_results(q):
    while True:
        future = q.get()
        if future is None:
            break
        else:
            consumer(future.result())

if __name__ == "__main__":
    sumlock = threading.Lock()
    result_queue = queue.Queue(MAX_QUEUE_SIZE)
    total = 0
    NUM_TO_DO = 1000
    with cf.ThreadPoolExecutor(NUM_THREADS) as tp:
        # start the threads running `consume_results`
        for _ in range(NUM_THREADS):
            tp.submit(consume_results, result_queue)
        # start the worker processes
        with cf.ProcessPoolExecutor(NUM_CPUS) as pp:
            for i in range(NUM_TO_DO):
                # blocks until the queue size <= MAX_QUEUE_SIZE
                result_queue.put(pp.submit(producer, i))
        # tell threads we're done
        for _ in range(NUM_THREADS):
            result_queue.put(None)
    print("got", total, "expected", (10 + NUM_TO_DO + 9) * NUM_TO_DO // 2)

谢谢你的回答,不幸的是你给了我太多的信任。我真的不懂线程/多处理的东西,因此需要一个很好的防白痴的例子。:-)我恐怕无法在我自己的代码上下文中找到您的答案;但是我已经编辑了我的问题,把我的“多重处理”的东西包括进去了。我对concurrent.futures感兴趣的是,它们可能会更易于使用/理解。稍后我将充实更多代码(现在没时间)。你说得对,
concurrent.futures
更容易使用,但它也更脆弱。唉,到目前为止,在您的mp代码中,您确实创建了一个
,但您从未使用过它-根本没有并行性。让我们一步一步来试试:-)啊,那是我的错别字;现在已使用pool.applyasync更正。在更大的数据集上,我的速度提高了4倍;另一方面,RAM使用从~10MB增加到~1.8GB!-所以这个问题。干杯。:-)查看我刚刚编辑的完整可执行示例。谢谢Tim;我已经尝试了超过30分钟,但无法让它工作。它是一个独立的函数,但当我尝试将问题中的“输出”函数放在那里时(我将它放在sumlock:
下面)-发生了两件事:(a)它开始超慢运行。(b) 尽管函数中有“print(2)”,但不会发生任何数据库操作。该函数将正确打印“2”。如果我像往常一样从“main”内部调用
output()
,那么db的东西就可以正常工作。我做错什么了吗?
import concurrent.futures as cf
import threading
import queue

NUM_CPUS = 3
NUM_THREADS = 4
MAX_QUEUE_SIZE = 20

# Runs in worker processes.
def producer(i):
    return i + 10

def consumer(i):
    global total
    # We need to protect this with a lock because
    # multiple threads in the main program can
    # execute this function simultaneously.
    with sumlock:
        total += i

# Runs in threads in main program.
def consume_results(q):
    while True:
        future = q.get()
        if future is None:
            break
        else:
            consumer(future.result())

if __name__ == "__main__":
    sumlock = threading.Lock()
    result_queue = queue.Queue(MAX_QUEUE_SIZE)
    total = 0
    NUM_TO_DO = 1000
    with cf.ThreadPoolExecutor(NUM_THREADS) as tp:
        # start the threads running `consume_results`
        for _ in range(NUM_THREADS):
            tp.submit(consume_results, result_queue)
        # start the worker processes
        with cf.ProcessPoolExecutor(NUM_CPUS) as pp:
            for i in range(NUM_TO_DO):
                # blocks until the queue size <= MAX_QUEUE_SIZE
                result_queue.put(pp.submit(producer, i))
        # tell threads we're done
        for _ in range(NUM_THREADS):
            result_queue.put(None)
    print("got", total, "expected", (10 + NUM_TO_DO + 9) * NUM_TO_DO // 2)
got 509500 expected 509500