编程经典案例:当线程遇到For循环,一个不可思议的Bug就出现了!
wptr33 2025-07-21 18:18 10 浏览
我们公司有个项目,需要视觉定位,大致就是在产品上会有一个“十字”形状的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的值不会变!
- 上一篇:js中的for循环(js中for循环执行机制)
- 已经是最后一篇了
相关推荐
- 编程经典案例:当线程遇到For循环,一个不可思议的Bug就出现了!
-
我们公司有个项目,需要视觉定位,大致就是在产品上会有一个“十字”形状的Mark标记,然后通过视觉相机连续拍照,然后将拍到的图片进行视觉算法运算,最终得出Mark标记的位置,然后根据其位置对设备进行位置...
- js中的for循环(js中for循环执行机制)
-
(for循环):for循环-语法:for(①初始化表达式;②条件表达式;④更新表达式){③语句...}...
- VUE循环语句的使用(v-for)(vuefor循环的key)
-
对数组进行遍历使用v-for进行遍历时注意参数格式,以“siteinsites”的格式填入参数,sites是被遍历的数据,site是遍历出的值。...
- 【200915】编程入门第五课,循环语句
-
介绍介绍...
- Linux,shell四种循环结构,for、while、until、select代码案例
-
Shell循环结构循环结构程序中使用循环就是模拟做重复的事情,一般情况下,语言都有循环语句,shell支持4种循环:for、while、until和select。...
- Python 中的 for 和 while 循环(for和while循环的区别python)
-
Python中的for和while循环<>6分钟阅读...
- 《循环(for/while)》(循环while语句)
-
循环(for/while)循环是编程中处理重复任务的核心工具,Python提供了两种主流循环结构:for循环(遍历可迭代对象)和while循环(根据条件重复执行)。本节将系统讲解两者的语法、使用场景及...
- Python学习记录(22)——for-in循环的学习
-
Python学习记录(22)——for-in循环的学习大家好,上一节课我们学习了Python学习记录(21)——while循环...
- 改几行代码,for循环耗时从3.2秒降到0.3秒,真正看懂的都是牛人
-
本文讲解一个非常重要的性能调优方法,会涉及到CPU内部非常重要的一些基础知识,为讲解清楚,篇幅较长,请务必看完,你一定会有收获!...
- Shell脚本小白教程 for循环(shell编程之for循环)
-
需求循环遍历for循环变量1、创建脚本touchfor.sh2、编辑脚本...
- PLC编程For循环:告别重复代码编程效率翻倍(附带注释案例)
-
一、For循环基础结构FOR循环变量:=起始值TO终止值BY步长DO//循环执行的代码END_FOR;...
- for...in 循环的坑,别再用它遍历 JavaScript 数组了!
-
在JavaScript开发中,...
- VBA代码实例之For循环嵌套的魅力(vba for条件循环嵌套)
-
第一种方法是用active插件复制这段代码:DimxAsInteger...
- Java循环:for、foreach与stream性能对比
-
性能比较如果数据在1万以内的话,for循环效率高于foreach和stream;如果数据量在10万的时候,stream效率最高,其次是foreach,最后是for。...
- 西门子博途SCL高级语言之FOR循环(西门子博途for循环语句编程)
-
FOR循环语句应用一1.)FOR循环语句介绍说明使用“在计数循环中执行”指令,重复执行程序循环,直至运行变量不在指定的取值范围内。也可以嵌套程序循环。在程序循环内,可以编写包含其它运行变量的其它程序...
- 一周热门
-
-
因果推断Matching方式实现代码 因果推断模型
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
git pull命令使用实例 git pull--rebase
-
git 执行pull错误如何撤销 git pull fail
-
面试官:git pull是哪两个指令的组合?
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
-
- 编程经典案例:当线程遇到For循环,一个不可思议的Bug就出现了!
- js中的for循环(js中for循环执行机制)
- VUE循环语句的使用(v-for)(vuefor循环的key)
- 【200915】编程入门第五课,循环语句
- Linux,shell四种循环结构,for、while、until、select代码案例
- Python 中的 for 和 while 循环(for和while循环的区别python)
- 《循环(for/while)》(循环while语句)
- Python学习记录(22)——for-in循环的学习
- 改几行代码,for循环耗时从3.2秒降到0.3秒,真正看懂的都是牛人
- Shell脚本小白教程 for循环(shell编程之for循环)
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)