news 2026/6/9 21:12:34

功能接口查询结果与原始数据差别大?可能是并发的锅

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
功能接口查询结果与原始数据差别大?可能是并发的锅

案情描述

收到这么一个需求:存在一个数据库查询功能接口,需要完成1000个条件语句的查询,并将查询结果与原始数据库(如es)的查询结果对比,从而判定该功能接口是否正常,且正确可用。

第一次测试设计:读取文件中的1000个查询条件,顺序执行。但由于查询数据量较大,单线程顺序完成1000次查询耗时较长,效率太低——抛弃;

第二次测试设计:使用并发查询,多线程并发提高工作效率,节省了大大的时间。但将输出的1000个查询结果与原始数据库查询结果对比时,发现某些语句差异较大,为什么?

抽丝剥茧

为何功能接口查询结果与原始数据查询结果差异性大?

经过排查,问题出在查询语句对比时匹配错位——使用简单并发查询,输出并不严格按照读入顺序。换句话说:读入1、2、3、4、5个顺序查询条件,并发查询输出顺序可能时2、1、3、4、5,也可能时4、5、2、1、3。

那么,如何在编写测试脚本时,让并发输出严格按照读入顺序输出呢?

亡羊补牢

从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,可以帮助我们更好地完成并发设计。

本文我们不过深地讲述如何使用这些Python类,只简单讲述如何解答我们的核心问题——“如何在并发时让结果严格按照输入顺序输出”。

线程池ThreadPoolExecutor

……

如下所示,为ThreadPoolExecutor的基本使用方法,打印输入的list列表中的数字。

ThreadPoolExecutor()初始化,创建线程池,最多2个线程并发运行,通过submit调用子函数(打印输入数字),最终通过as_completed等待所有任务完成后,通过result收集返回结果。

  1. # -*-coding:utf-8 -*-

  2. from concurrent.futures import ThreadPoolExecutor,as_completed

  3. # 子函数,打印输入数字

  4. def print_num(num):

  5. return num

  6. list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]

  7. ##############使用ThreadPoolExecutor###################

  8. ###############并发执行,非顺序返回################

  9. # 创建线程池子

  10. # 设置最多2个线程运行,其他等待

  11. executor = ThreadPoolExecutor(max_workers=2)

  12. all_task = [executor.submit(print_num, (num)) for num in list]

  13. result2_list = []

  14. for future in as_completed(all_task):

  15. data = future.result()

  16. result2_list.append(data)

  17. print("并发执行,非顺序返回:"+ str(result2_list))

  18. ##############################################

如下图所示为上述使用ThreadPoolExecutor并发运行的结果,由结果打印可知,输出并非严格按照列表输入值顺序输出。由此可以预见,当我们简单使用并发时,我们的结果可能并不是我们认为的与“顺序”输出。

那么,如何改造呢?请关注map方法。

使用map方法,无需提前使用submit方法,map方法与python标准库中的map含义相同,都是将序列中的每个元素都执行同一个函数。

  1. ###############并发执行,顺序返回################

  2. # 创建线程池子

  3. # 设置最多2个线程运行,其他等待

  4. executor = ThreadPoolExecutor(max_workers=2)

  5. result1_list = []

  6. for result in executor.map(print_num, list):

  7. result1_list.append(result)

  8. print("并发执行,顺序返回:" + str(result1_list))

  9. ##############################################

上面的代码就是对list的每个元素都执行print_num子函数,并分配各线程池。

由此可以看到,执行结果与上面的as_completed方法的结果不同,输出顺序和list列表的顺序相同。

进程池ProcessPoolExecutor

……

使用进程池ProcessPoolExecutor处理并发输入,按序输出问题,同ThreadPoolExecutor一样,使用map方法即可解决。但是值得注意的是,注意,在使用多进程时,必须把 多进程代码写在?if __name__ == '__main__' 下面,否则异常,甚至报错“concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.”

  1. #########################使用ProcessPoolExecutor#####################

  2. ###############并发执行,顺序返回################

  3. # 创建进程池子

  4. # 设置最多2个进程运行,其他等待

  5. if __name__ == '__main__':

  6. executor = ProcessPoolExecutor(max_workers=2)

  7. result2_list = []

  8. for result in executor.map(print_num, list):

  9. result2_list.append(result)

  10. print("ProcessPoolExecutor并发执行,顺序返回:" + str(result2_list))

  11. ##############################################

  12. #####################################################################

还存在一点“笨”方法

……

除了上述所说的ThreadPoolExecutor和ProcessPoolExecutor使用map方法让结果顺序输出外,我们还可以使用一些笨方法。

例如,使用并发运行程序(并发方法不仅限于ThreadPoolExecutor和ProcessPoolExecutor,还可以使用threading等等)sort方法对输出结果排序。就上述案例而言,不使用map可以如下改造:​​​​​​​

  1. ###############并发执行,非顺序返回################

  2. # 创建线程池子

  3. # 设置最多2个线程运行,其他等待

  4. executor = ThreadPoolExecutor(max_workers=2)

  5. all_task = [executor.submit(print_num, (num)) for num in list]

  6. result2_list = []

  7. for future in as_completed(all_task):

  8. data = future.result()

  9. result2_list.append(data)

  10. result2_list.sort()

  11. print("ThreadPoolExecutor并发执行,非顺序返回:"+ str(result2_list))

  12. ##############################################

最终,输出结果符合我们的要求。

结案陈词

ThreadPoolExecutor 多线程并行执行任务,可以共享当前进程变量,但缺点也很致命,但其实仍然最多只能占用 CPU 一个核。

如果指定的任务和线程数不恰当(比如一个任务很短,线程数量很多,导致线程频繁调用回收),那么效率还不如单线程。ProcessPoolExecutor可以使用多核进行计算,但缺点就是进程之间共享数据就比较麻烦,消耗更多的内存。

本文的主要目的是帮助大家认识:并发好用,但使用需谨慎。谨防一不小心落入并发的坑,使用map方法可以帮助大家快速地完成测试结果地有序输出。

感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 19:16:41

Java面试精讲:跨境物流场景下的JVM、Git与Jakarta EE深度剖析

Java面试精讲:跨境物流场景下的JVM、Git与Jakarta EE深度剖析 📋 面试背景 欢迎来到“宇宙大厂”的Java高级开发工程师面试现场。今天,我们要面试的候选人是“小润龙”,一位在技术路上充满激情但偶尔也会“跑偏”的程序员。面试官…

作者头像 李华
网站建设 2026/6/7 6:39:35

室友分享的7个降AI工具,论文ai率从80%降低到13%!

市场上的降AI率工具良莠不齐,如何科学判断降AI率效果是很多学生、老师最关心的问题,担心降不来AI率,耽误时间还花不少钱。 本文将从以下五个维度系统,分析2025年主流的8个降AI工具,教大家如何选择适合自己的降AIGC工具…

作者头像 李华
网站建设 2026/6/7 1:17:25

降ai率太贵了?嘎嘎降免费1000字降AI,去aigc痕迹嘎嘎快!

市场上的降AI率工具良莠不齐,如何科学判断降AI率效果是很多学生、老师最关心的问题,担心降不来AI率,耽误时间还花不少钱。 本文将从以下五个维度系统,分析2025年主流的8个降AI工具,教大家如何选择适合自己的降AIGC工具…

作者头像 李华
网站建设 2026/6/9 6:14:00

GraphRAG实战指南:知识图谱与RAG结合,打造可靠AI应用!

简介 GraphRAG融合知识图谱与RAG技术,解决LLM缺乏领域知识、易产生幻觉等问题。通过结构化知识提供准确可解释的上下文,实现更智能的AI应用。实现分为知识图谱构建和图检索两阶段,前期投入可在检索阶段获得多倍回报,为企业级AI应…

作者头像 李华
网站建设 2026/6/7 12:30:41

物流异常通知:LobeChat自动生成安抚话术

物流异常通知:LobeChat 自动生成安抚话术 在电商大促后的清晨,客服系统突然涌入上千条“我的快递怎么还没到”的消息。客户焦虑、物流延迟、人工响应滞后——这一幕在各大平台并不罕见。面对高并发的咨询压力,尤其是涉及包裹延误、丢失等情绪…

作者头像 李华
网站建设 2026/6/9 12:44:18

C++中的安全指针(智能指针)

C中的安全指针(智能指针)主要用于自动内存管理,避免内存泄漏和悬挂指针。主要有以下几种: 1. 标准库智能指针 unique_ptr(独占指针) 特点:独享所有权,不可复制,可移动适用…

作者头像 李华