我想使用concurrent.futures模块启用程序的并行处理/线程化。
不幸的是,我似乎无法找到使用concurrent.futures模块的任何好的,简单的,笨拙的例子。它们通常需要更高级的python知识或处理/线程概念和行话。
下面是一个基于我的程序的简化的自包含示例:有一个纯粹的CPU绑定任务,适用于多路径,以及插入数据库(SQLite)的单独IO绑定任务。 在我的程序中,我已经将其转换为使用多处理池类,但由于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)
答案 0 :(得分:13)
concurrent.futures
有一个简约的API。它很容易用于非常简单的问题,但是你没有一个非常简单的问题。如果你这样做了,你就已经解决了它; - )
你没有显示你编写的任何multiprocessing.Pool
代码,但这将是一个更有希望开始的地方 - 假设你想要解决问题而不是想要确认你希望它必须是如果您只切换到较弱的API,则很容易做到; - )
使用multiprocessing
继续进行“显而易见”的方法是使用Pool.apply_async()
方法,将异步结果对象放在有界Queue.Queue
上,并在主程序中包含线程离开Queue
并等待结果出现。这很容易,但它并不神奇。它解决了您的问题,因为有界Queues
是 规范的方式,可以在不同速度的生产者和消费者之间进行调解。 concurrent.futures
中的任何内容都没有直接解决 问题,而且它是“大量内存”问题的核心。
# Define global result_queue only in the main program.
import Queue
result_queue = Queue.Queue(100) # pick a reasonable max size based on your problem
# Run this in as many threads as you like.
def consume_results():
while True:
a = result_queue.get()
if a is None:
break
output(a.get()) # `output()` is your function
...
# main program passes out work, after starting threads
for i in range(1000):
# the .put() will block so long as the queue is at its max size
result_queue.put(pool.apply_async(calculate, args=(i,)))
# add sentinels to let threads know they're done
for i in range(number_of_threads_you_started):
result_queue.put(None)
这就是你需要让生产者和消费者大致保持平衡的东西,并且任何标准库中都没有任何东西可以通过魔法为你做到这一点。
编辑 - 充实它
这是一个完整的可执行示例,任何拥有Python3的人都可以运行。注意:
concurrent.futures
来管理进程和线程。使用multiprocessing
和threading
并不是很难,事实上 way 线程在这里使用,直接使用threading
会更容易一些。但这种方式很清楚。concurrent.futures
Future
对象与multiprocessing
异步结果对象基本相同 - API功能的拼写方式不同。MAX_QUEUE_SIZE
。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)
如果一切顺利,这是预期的输出:
got 509500 expected 509500