百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

编程经典案例:当线程遇到For循环,一个不可思议的Bug就出现了!

wptr33 2025-07-21 18:18 56 浏览

我们公司有个项目,需要视觉定位,大致就是在产品上会有一个“十字”形状的Mark标记,然后通过视觉相机连续拍照,然后将拍到的图片进行视觉算法运算,最终得出Mark标记的位置,然后根据其位置对设备进行位置纠正。但是,想不到就这么一个小功能却出现了一个Bug,找了很久才发现原因!

说这个Bug之前,我需要先说下这个Bug是怎么被我写出来的!

甲方那有一个设备,设备上有一个可以前后移动的轴,下称M轴,轴上又装了一个可以左右移动的轴,类似于贴片机的构造吧。我们用来视觉定位的相机就是装在那个可以左右移动的轴上面的,下面简称P轴。

Mark标记处于P轴的初始位置,而且Mark的左右视野范围被设定成不可超过P轴的初始位置,因此,要用Mark进行定位,只需要M轴前后移动,相机进行拍照即可,最后再根据Mark的位置调整P轴的位置。

最开始,我选择的是一张一张得拍,并且我在封装相机SDK的时候,取图我只封装了一个取单张图片的接口。

后来发现算法速度跟不上M轴移动的速度,当相机拍了一张照片,然后程序再对照片进行视觉运算,中间会产生一定时间的间隙,当程序算完以后,M轴已经移动很远了,拍到下一张图片的时候,信息就会丢失一部分空间画面。

如果Mark恰巧处于这个空间之内,那么很大概率就拍不到Mark标记了!

所以,我又在相机SDK里面重新封装了一个接口,用来批量取图,然后返回图片地址列表,大致的逻辑就是根据视觉相机设置的帧率乘以M轴移动的总时间,换算出相机应该拍照的总照片数,假设相机帧率是10帧每秒,M轴总移动时间是3秒,那么应该取出的图片就是10*3=30张。

这么做的目的就是在M轴移动的时候,纯拍照片,然后将拍到的图片保存在缓存目录里面,其他什么都不做,这样能让视觉相机使用最大帧率去取图,从而覆盖M轴行程内的所有空间。最后,循环所有已经拍到的图片,总有一张或者几张图片里面存在Mark标记!最后,再一次性返回所有图片的保存地址。

虽然这个方案很好,但是在实际运行过程中却出现了一个小的Bug,那就是Mark的检测结果和实际展示图片对不上!并且,还不是每次都会出现这样的Bug,有时候就是好的。

症状就是,很多时候显示的图片里面明明没有Mark标记,但是算法却能识别出Mark标记来,或者有Mark标记,但是Mark标记被检测出来的上下位置却不一样,可偏偏结果还是正确的!

最开始,我以为是算法有问题,后来这个问题很快被排除了。

那会不会是在循环图片时,把图片索引搞错了呢?我仔细看了下我循环图片,对图片进行视觉运算的那段代码,也没发现问题。

本来,这个问题其实很好解决的,只要下个断点调试一下就能发现问题出在哪,可偏偏软件已经部署在了甲方那里,问题只能通过我们公司放在甲方那边的同事跟我复述,我通过在代码里面加运行日志来分析问题出现的原因。

因为日志总归是人加的,它只能被加在我可能认为会出问题的地方,一些我不认为会出现问题的地方我是不会加的。

但偏偏有一个地方我就忽略了!

前面说了,我在视觉相机的SDK里面重新封装了一个批量取图的接口,而这个接口的逻辑是通过一个触发拍照命令,输入指定数量的图片和取图时间来取图的,取出来的图是放在一个指针数组里面的,我需要循环将指针转换成图片对象,然后再保存到缓存目录里。

因为图片数量太多,为了保证转换效率,所以,我在循环指针数组的时候,使用了线程池来转换图片,问题就出在这!

我外层使用的是一个普通的循环,因为我不光要取图,还需要保存图片,而图片名称的某一个规则就是循环的当前索引,如“20250624_0_.jpg”,其中“_0_”中的0就是循环索引i的值。

因为使用的是线程池来执行取图逻辑,那么在当前线程里面直接取i是有问题的!因为i的作用域是在循环内部,但是,循环内部如果执行的是一个线程,那么每一个线程所获得到的i和这个线程的作用域不一样,那么就会导致这个线程取到的i实际并不是顺序的!

这么说不理解的话,我举一个极端的例子:

假设我们需要拍30张图片,这30张图片被存在了一个指针数组里,我们通过For循环去循环指针数组,每循环一次,在线程池里面加一个线程,用来将指针转换成图片,而此时,我们PC的线程数已经被其他程序占用,因此,这30个线程都处于等待状态,直到最后一次循环,此时i为30,循环完成后,此时前面被占用的线程瞬间被释放,前面等待的这30个线程将全部执行,此时,这30个线程里面取到的所有i值将都是30!

而此时保存图片,所有图片名称都是一样的,因为图片保存走的是覆盖流程,最后程序执行下来,只会保存住一张图片!

如果还不懂,请看示例:

这就是导致前面所有问题的根源!因为我在执行批量取图之后,我需要重新循环图片地址列表去读取图片,然后对图片进行视觉运算以及显示。

结合上面所说,如果最后30张图片所有的i都是30的话,那么最终保存的图片数量就只有一张了,此时,图片数量就是1。

此时,我如果使用For循环这么写:

For(int I = 0;i<1;i++)

这么一来,这个循环索引0所获取到的图片实际上是 “20250624_30_.jpg”,此时,如果视觉算法通过了,我会将图片索引保存,此时保存的所谓的图片索引就是0。

显然,索引为0的图片并不存在,因此,在图片显示的时候,这时候画面上就只有Mark标记框,而没有实际图片。

那假设批量取图数量是30,但是因为线程的原因,最终保存在文件夹里的图片只有15张会出现什么结果呢?

很显然,这时候循环索引和所有图片的实际索引就都错位了!

我也在批量取图后的那个循环读取图片的For循环里面也加过日志,但是恰巧当时这个问题没出现,我以为程序已经没问题了,所以就将日志给去除了,后来再出问题时,就没往这方面想了。

至于为什么不保存图片地址而保存图片循环的索引,这个是业务逻辑决定的。

当然,这么写会出问题我是知道的,但是,您也知道,有时候写代码的事很难说,一个不注意写出一个Bug其实很难发现的。

直到我查遍所有代码,把所有可能性都一一排除以后,最后瞄了一眼我写的相机SDK,一眼就发现了问题所在!

总结

在For循环中使用线程,并且在线程里面使用For循环的索引导致的错误,其实这是一个很经典的线程安全案例,我在过去接触到的很多年轻的程序员在使用线程的时候基本上都会遇到这个问题。

如何避免这个问题呢?其实很简单,就是强制将For循环的作用域限制到当层循环之内,写法就是:

前后对比下,我只是将直接使用i的方式改成了先将i这个循环索引先赋值给变量index,然后再去使用,这样,index这个变量的作用域就会被强制锁定在当前循环内,不管最终线程什么时候执行,index的值不会变!

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...