python 多线程 加速处理列表数据

背景

最近使用大模型进行推理时,首先将大模型 api 部署,然后借助于 request库,发送请求。

在之前写同步的请求,必须等到上一次request响应的结果回来后,才能发送下一次请求,这样导致速度非常慢。

由于大模型部署支持并发,故打算采用多线程方式,让大模型快速处理。

简介

  • 多线程

多线程示例代码

python 复制代码
import concurrent
from concurrent.futures import ThreadPoolExecutor  
from tqdm import tqdm  
import time  

start = time.time()
# 假设我们有一个URL列表,这里用占位符代替  
urls = [i for i in range(25)]  

# 模拟发送网络请求  
def send_request(url):
    print(f"发送请求 {url}")
    time.sleep(1)  # 模拟网络延迟
    print(f"收到请求 {url} 的响应")
    return url

# 使用ThreadPoolExecutor创建线程池,并限制线程数为 max_workers
with ThreadPoolExecutor(max_workers=5) as executor:  
    # 使用executor的map方法将urls列表中的每个URL映射到send_request函数  
    # 这会自动将任务分配给线程池中的线程,而且 results_iterator 收集到的返回值的结果
    results_iterator = executor.map(send_request, urls)
    
end = time.time()

print(f"总共耗时 {end - start} 秒")
python 复制代码
print(list(results_iterator))

results_iterator 收集到的返回值是有序的

多线程的运行结果如下所示:

bash 复制代码
发送请求 0
发送请求 1
发送请求 2
发送请求 3
发送请求 4
收到请求 0 的响应收到请求 3 的响应
发送请求 5
收到请求 4 的响应
发送请求 6

发送请求 7
收到请求 1 的响应
发送请求 8
收到请求 2 的响应
发送请求 9
收到请求 5 的响应
发送请求 10
收到请求 6 的响应
发送请求 11
收到请求 7 的响应
发送请求 12
收到请求 9 的响应
发送请求 13
收到请求 8 的响应
发送请求 14
...
收到请求 24 的响应
收到请求 22 的响应

总共耗时 5.021236896514893 秒

关于 核心数选取多少:

在Python中,多线程的性能并不总是随着线程数的增加而线性提升,尤其是在执行CPU密集型任务时。这是因为Python的全局解释器锁(GIL)限制了一次只能有一个线程执行Python字节码。所以,即使你的机器有多个CPU核心,Python的多线程在并行执行CPU密集型任务时可能并不会充分利用所有的核心。

然而,如果你的线程主要执行I/O操作(如网络请求、文件读写等),那么多线程可以有效地利用等待时间,从而提高程序的总体效率。在这种情况下,线程数可以超过CPU核心数,因为线程会在等待I/O操作时自动切换。

对于CPU密集型任务,如果你想要充分利用多核CPU的性能,更好的选择是使用多进程(multiprocessing)而不是多线程。Python的multiprocessing库可以创建多个进程,每个进程都有自己的Python解释器,从而可以并行执行CPU密集型任务。

至于具体应该设置多少线程或进程,这取决于你的具体需求和机器的配置。一般来说,对于I/O密集型任务,线程数可以设置为略多于CPU核心数,以便在等待I/O时充分利用CPU。对于CPU密集型任务,进程数可以设置为等于或略少于CPU核心数,以避免过多的上下文切换开销。但请注意,这只是一个一般性的建议,具体的最佳值可能需要通过实验来确定。

对比 不使用多线程

python 复制代码
start = time.time()
# 假设我们有一个URL列表,这里用占位符代替  
urls = [i for i in range(25)]  

# 定义一个函数来发送网络请求  
def send_request(url):
    print(f"发送请求 {url}")
    time.sleep(1)  # 模拟网络延迟
    print(f"收到请求 {url} 的响应")
    return url

ans = []
for url in urls:
    ans.append(send_request(url))
    
end = time.time()

print(f"总共耗时 {end - start} 秒")
bash 复制代码
发送请求 0
收到请求 0 的响应
发送请求 1
收到请求 1 的响应
发送请求 2
收到请求 2 的响应
发送请求 3
收到请求 3 的响应
发送请求 4
收到请求 4 的响应
发送请求 5
收到请求 5 的响应
发送请求 6
收到请求 6 的响应
发送请求 7
收到请求 7 的响应
发送请求 8
收到请求 8 的响应
发送请求 9
收到请求 9 的响应
发送请求 10
收到请求 10 的响应
发送请求 11
收到请求 11 的响应
发送请求 12
...
收到请求 23 的响应
发送请求 24
收到请求 24 的响应
总共耗时 25.100407123565674 秒

如果不使用多线程的,必须收到上一次请求的响应,才能发送下一个请求;总共耗时25秒,而多线程只需要5秒;

相关推荐
以卿a19 分钟前
C++ 模板初阶
开发语言·c++
s:10323 分钟前
【框架】参考 Spring Security 安全框架设计出,轻量化高可扩展的身份认证与授权架构
java·开发语言
道不尽世间的沧桑1 小时前
第17篇:网络请求与Axios集成
开发语言·前端·javascript
久绊A1 小时前
Python 基本语法的详细解释
开发语言·windows·python
Hylan_J5 小时前
【VSCode】MicroPython环境配置
ide·vscode·python·编辑器
软件黑马王子5 小时前
C#初级教程(4)——流程控制:从基础到实践
开发语言·c#
莫忘初心丶5 小时前
在 Ubuntu 22 上使用 Gunicorn 启动 Flask 应用程序
python·ubuntu·flask·gunicorn
闲猫5 小时前
go orm GORM
开发语言·后端·golang
李白同学6 小时前
【C语言】结构体内存对齐问题
c语言·开发语言
黑子哥呢?8 小时前
安装Bash completion解决tab不能补全问题
开发语言·bash