如何在Python中分配工作给一堆工作线程?
在某些场合下,我们需要通过多线程来处理一批任务。比如,我们需要下载一系列文件,或者在一段时间内从多个传感器中读取数据。当处理任务数量巨大时,单个线程可能无法满足处理需求,这时候我们就需要用到线程池,来同时处理多个任务。
线程池是什么?
线程池是一种预先创建的有固定数量线程的池子。对于一个任务请求,会从线程池中请求一个线程来处理请求任务。当任务处理完成后,线程不会被销毁,而是继续留在线程池中等待下一个任务。这种做法相比于为每一个任务开启一个新的线程,可以有效地降低线程创建和销毁的开销,提高程序的效率。
Python为我们提供了concurrent.futures
模块,使用这个模块,我们可以轻松地创建线程池并按需分配任务。
如何使用concurrent.futures
?
我们可以使用ThreadPoolExecutor
类来创建一个线程池,并用它来提交任务。
创建线程池
我们可以使用如下方法来创建一个线程池:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=5) as executor:
# code ...
这里,max_workers
是我们要创建的线程池的最大线程数。当我们使用完这个线程池后,使用with
语句来确保线程池会被正确地终止并释放资源。
提交任务
提交任务可以使用类似于下面这样的语句:
futures = []
for i in range(10):
futures.append(executor.submit(my_function, arg1, arg2))
这里,我们使用了executor.submit
方法来提交任务,它的第一个参数是我们要执行的函数,后面的参数是函数的参数。返回值是一个Future
对象的列表,这个列表将会在后面被用到。
使用Future
对象
Future
对象是concurrent.futures
的一个重要概念。它代表一个未来的结果,可以用来查看任务的状态和返回值。我们可以通过result
方法来取得任务的返回值,如果在任务完成前调用result
方法,主线程会在任务执行完成后阻塞等待。
for future in futures:
print(future.result())
如果我们有多个Future
对象,但是希望不论哪一个完成都立即返回,可以使用as_completed
方法:
from concurrent.futures import as_completed
for future in as_completed(futures):
print(future.result())
ThreadPoolExecutor
与ProcessPoolExecutor
concurrent.futures
模块中还有一个ProcessPoolExecutor
类,它可以方便地创建进程池,与线程池类似。但是需要注意的是,由于进程之间无法共享内存的限制,使用进程池时需要将数据序列化后再传递给子进程。
完整示例
接下来,我们来看一个完整的示例。这个示例中,我们要从三个URL中下载数据,并计算每个URL所返回数据的总长度。
import requests
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
URLS = [
'https://www.baidu.com/',
'https://www.sina.com.cn/',
'https://www.jianshu.com/',
]
def download(url):
response = requests.get(url)
return url, len(response.content)
if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=5) as executor:
futures = []
for url in URLS:
futures.append(executor.submit(download, url))
for future in as_completed(futures):
url, length = future.result()
print(f'{url}的长度为{length}字节')
这个示例中,我们首先定义了要下载数据的三个URL,并将它们存储在一个列表中。接着,我们定义了一个download
函数,用于下载URL对应的数据,并返回URL和数据长度。在主程序中,我们使用ThreadPoolExecutor
类来创建一个线程池,并使用executor.submit
方法将每个URL对应的任务提交到线程池中。最后,我们通过as_completed
方法来获取每个任务的结果,并打印出每个URL对应的数据长度。
结论
使用concurrent.futures
模块,我们可以方便地创建线程池,并用它来并发地执行多个任务。这些任务可以是函数调用、URL下载、文件读写等常见操作。通过线程池的方式,我们可以避免频繁地创建和销毁线程所带来的开销,从而提高程序的效率和可维护性。