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秒;

相关推荐
无聊写博客4 分钟前
JDK、JRE、JVM的区别
java·开发语言·jvm
黑不溜秋的5 分钟前
C++ 编程指南04 - 尽量编写静态类型安全的程序
开发语言·c++·安全
股票GPT分析12 分钟前
《Python 股票交易分析:开启智能投资新时代》(二)
大数据·服务器·python·c#·fastapi
j1780505690613 分钟前
C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用-腾讯云开发者社区-腾讯云
开发语言·c#
搬砖的小码农_Sky17 分钟前
C语言:字符串
c语言·开发语言
大熊程序猿18 分钟前
python Flask指定IP和端口
开发语言·python·flask
我叫白小猿19 分钟前
【大模型-智能体】AutoGen Studio测试和导出工作流程
人工智能·python·workflow·工作流·智能体·autogen
K2SO4钾19 分钟前
16. 清理Python包管理工具(pip 和 conda)的缓存和冗余文件
python·conda·pip
YONG823_API25 分钟前
1688商品数据采集API的测试对接步骤分享(提供免费测试key)
开发语言·数据库·爬虫·python·数据挖掘
好看资源平台33 分钟前
网络爬虫总结与未来方向
爬虫·python