Python 高手编程系列四百零一:使用线程池

我们尝试解决的第一个问题是程序运行中未绑定限制的线程。一个好的解决方案是构
建一个严格定义大小的工作线程池,它将处理所有的并行工作,并通过一些线程安全的数
据结构与工作线程进行通信。通过使用这个线程池的方案,我们还会很容易地解决我们刚
才提到的另外两个问题。
所以通常的想法是启动一些预定义数量的线程,它将从队列中消费工作项,直到完成。
当没有其他工作要做时,线程将返回,我们将能够退出程序。对于我们的结构,Queue 类
是用于与工作线程通信的很好的选择,它来自内置的 queue 模块。它是一个 FIFO(先进
先出)队列实现,它非常类似于 collections 模块的 deque 集合,专门设计用于处理线
程间通信。这里是一个修改版本的 main()函数,它只启动有限数量的工作线程,并使用
一个新的 worker()函数作为目标,并使用线程安全的队列与它们通信,如下所示:
from queue import Queue, Empty
from threading import Thread
THREAD_POOL_SIZE = 4
def worker(work_queue):
while not work_queue.empty():
try:
item = work_queue.get(block=False)
except Empty:
break
else:
fetch_place(item)
work_queue.task_done()
def main():
work_queue = Queue()
for place in PLACES:
work_queue.put(place)
threads = [
Thread(target=worker, args=(work_queue,))
for _ in range(THREAD_POOL_SIZE)
]
for thread in threads:
thread.start()
work_queue.join()
while threads:
threads.pop().join()
运行这个修改版本的程序的的结果类似于前一个,如下所示:
$ python threadpool.py
Reykjavík, Iceland, 64.13, -21.82
Venice, Italy, 45.44, 12.32
Vienna, Austria, 48.21, 16.37
Zadar, Croatia, 44.12, 15.23
Wrocław, Poland, 51.11, 17.04
Bologna, Italy, 44.49, 11.34
Slubice, Poland, 52.35, 14.56
Berlin, Germany, 52.52, 13.40
New York, NY, USA, 40.71, -74.01
Dehli, Gujarat, India, 21.57, 73.22
time elapsed: 1.20s
运行时间比为每个参数启动一个线程的情况更慢,但是至少现在不可能被任意长的输
入耗尽所有计算资源。此外,我们可以调整 THREAD_POOL_SIZE 参数以获得更好的资源/
时间的平衡。
使用双向队列
我们现在能够解决的另一个问题是在线程中输出的潜在的有问题的打印。更好的方式
是启动另外的线程打印,而不是在主线程中进行。我们可以通过提供另一个队列来处理它,
这个队列将负责从我们的工作线程中收集结果。这里是完整的代码,把一切都放到一起,
对主要的变化进行突出显示,如下所示:
import time
from queue import Queue, Empty
from threading import Thread
from gmaps import Geocoding
api = Geocoding()
PLACES = (
‘Reykjavik’, ‘Vien’, ‘Zadar’, ‘Venice’,
‘Wrocław’, ‘Bolognia’, ‘Berlin’, ‘Słubice’,
‘New York’, ‘Dehli’,
)
THREAD_POOL_SIZE = 4
def fetch_place(place):
return api.geocode(place)[0]
def present_result(geocoded):
print(“{:>25s}, {:6.2f}, {:6.2f}”.format(
geocoded[‘formatted_address’],
geocoded[‘geometry’][‘location’][‘lat’],
geocoded[‘geometry’][‘location’][‘lng’],
))
def worker(work_queue, results_queue):
while not work_queue.empty():
try:
item = work_queue.get(block=False)
except Empty:
break
else:
results_queue.put(
fetch_place(item)
)
work_queue.task_done()
def main():
work_queue = Queue()
results_queue = Queue()
for place in PLACES:
work_queue.put(place)
threads = [
Thread(target=worker, args=(work_queue, results_queue))
for _ in range(THREAD_POOL_SIZE)
]
for thread in threads:
thread.start()
work_queue.join()
while threads:
threads.pop().join()
while not results_queue.empty():
present_result(results_queue.get())
if name == “main”:
started = time.time()
main()
elapsed = time.time() - started
print()
print(“time elapsed: {:.2f}s”.format(elapsed))
这消除了格式化输出的风险,如果 present_result()函数执行更多的 print()语
句或执行一些额外的计算,我们可以体验到。在小的输入下,我们不奢望这种方法会有任
何性能改进,但实际上,我们还减少由于缓慢的 print()执行的线程串行化的风险。这是
我们的最终的输出如下:
$ python threadpool_with_results.py
Vienna, Austria, 48.21, 16.37
Reykjavík, Iceland, 64.13, -21.82
Zadar, Croatia, 44.12, 15.23
Venice, Italy, 45.44, 12.32
Wrocław, Poland, 51.11, 17.04
Bologna, Italy, 44.49, 11.34
Slubice, Poland, 52.35, 14.56
Berlin, Germany, 52.52, 13.40
New York, NY, USA, 40.71, -74.01
Dehli, Gujarat, India, 21.57, 73.22
time elapsed: 1.30s

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值