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

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

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

我们公司有个项目,需要视觉定位,大致就是在产品上会有一个“十字”形状的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的值不会变!

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...