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

相关推荐
懒大王爱吃狼28 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
秃头佛爷1 小时前
Python学习大纲总结及注意事项
开发语言·python·学习
待磨的钝刨2 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
深度学习lover3 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
API快乐传递者4 小时前
淘宝反爬虫机制的主要手段有哪些?
爬虫·python
励志成为嵌入式工程师4 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端