线程池放在局部和成员的区别

本文探讨了线程池在不同场景下可能引发的内存泄露问题,包括线程池作为局部变量和成员变量时的注意事项,以及如何通过设置核心线程超时时间和主动调用shutdown方法来避免内存泄露。

1.线程池释放的情况

有核心线程数的线程池,线程池是不会主动释放的,除非线程全都死掉了,线程池才随之释放

 

2.线程池作为局部变量

在调用该方法的时候就会频繁的创建线程池的实例,如果在没有手动调用shutdown方法的情况下会导致内存泄露。如果非要放在局部不可,需要对核心线程设置超时时间并且主动调用shutdown关闭线程池

 

3.线程池作为成员变量

这要分两种情况:

(1)如果宿主对象需要被频繁的创建对象,也就是多例,那么即使线程池放在成员,也会导致存在很多的线程池实例,导致内存泄露。

(2)在springmvc中就不会有这样的情况,因为被spring管理的类都是单例的,那么成员处的线程池使用静态修饰后,内存中只存在一个实例

 

.版本 2 .支持库 iext .支持库 EThread .程序集 窗口程序集_启动窗口 .程序集变量 许可证, 线程许可证 .程序集变量 结果文件号, 整数型 .程序集变量 线程数, 整数型 .程序集变量 停止标志, 逻辑型 .程序集变量 目录队列, 文本型, , "0" .程序集变量 最大线程数, 整数型 .程序集变量 队列许可证, 线程许可证 .程序集变量 文件写入锁, 线程许可证 .程序集变量 UI更新许可证, 线程许可证, , , ; 新增UI更新锁 .程序集变量 文件路径, 文本型 .程序集变量 工作线程数, 整数型 .子程序 __启动窗口_创建完毕 花名花见.皮肤加载 (6, , , , , ) 通用对话框_保存.初始目录 = 编辑框_目录.内容 最大线程数 = 4 ' 根据CPU核心数设置合适值 清除数组 (目录队列) ' 初始化目录队列 许可证 = 线程_创建许可证 () 队列许可证 = 线程_创建许可证 () 文件写入锁 = 线程_创建许可证 () UI更新许可证 = 线程_创建许可证 () ' 创建UI更新锁 结果文件号 = 打开文件 (“文件列表.txt”, #重写, ) 关闭文件 (结果文件号) 停止标志 = 假 编辑框_目录.内容 = 到文本 (读入文件 (取运行目录 () + “\地址.ini”)) 超级列表框_数据显示.始终显示选择项 = 真 最大线程数 = 5 ' 初始化线程池 .计次循环首 (最大线程数, ) 启动线程 (&目录扫描线程, , ) .计次循环尾 () .子程序 _按钮_查找文件_被单击 .局部变量 初始目录, 文本型 .局部变量 文本, 文本型 文本 = 文本_删除空行 (编辑框_过滤器.内容) .如果真 (文本 = “”) 信息框 (“未输入需要查找的文件名!”, 0, , ) 返回 () .如果真结束 ' 在主线程直接更新UI 标签_搜索完成.文本颜色 = #红色 标签_搜索完成.标题 = “文件枚举中,请稍等》》》》” 列表框1.清空 () 初始目录 = 编辑框_目录.内容 .如果真 (目录_是否存在 (初始目录) = 假) 信息框 (“目录不存在!”, 0, , ) 返回 () .如果真结束 停止标志 = 假 列表框1.清空 () ' 初始化目录队列 清除数组 (目录队列) 加入成员 (目录队列, 初始目录) ' 启动多个工作线程 .计次循环首 (最大线程数, ) 启动线程 (&目录扫描线程, , ) .计次循环尾 () .子程序 目录扫描线程 .局部变量 文件数组, 文本型, , "0" .局部变量 子目录数组, 文本型, , "0" .局部变量 当前目录, 文本型 .局部变量 i, 整数型 .局部变量 缓存路径, 文本型 .局部变量 tmp_count, 整数型 .局部变量 tmp_workers, 整数型 .判断循环首 (取反 (停止标志)) 线程_进入许可区 (许可证) .如果 (取数组成员数 (目录队列) > 0) 当前目录 = 目录队列 [1] 删除成员 (目录队列, 1, 1) 工作线程数 = 工作线程数 + 1 .否则 当前目录 = “” .如果结束 线程_退出许可区 (许可证) .如果真 (当前目录 = “”) 程序_延时 (100) 到循环尾 () .如果真结束 文件_枚举 (当前目录, 编辑框_过滤器.内容 + “.txt”, 文件数组, 真, 假, 真) 缓存路径 = “” ' 初始化缓存 .计次循环首 (取数组成员数 (文件数组), i) .如果真 (停止标志) 跳出循环 () .如果真结束 缓存路径 = 缓存路径 + 文件数组 [i] + #换行符 线程_进入许可区 (UI更新许可证) ' 添加UI锁 列表框1.加入项目 (文件数组 [i], ) 线程_退出许可区 (UI更新许可证) .计次循环尾 () .如果真 (缓存路径 ≠ “”) 保存结果 (缓存路径) ' 批量写入 .如果真结束 目录_枚举子目录 (当前目录, 子目录数组, 假) 线程_进入许可区 (许可证) .计次循环首 (取数组成员数 (子目录数组), i) 加入成员 (目录队列, 子目录数组 [i]) .计次循环尾 () 工作线程数 = 工作线程数 - 1 tmp_count = 取数组成员数 (目录队列) tmp_workers = 工作线程数 线程_退出许可区 (许可证) ' 安全更新UI 线程_进入许可区 (UI更新许可证) .如果 (tmp_count = 0 且 tmp_workers = 0) 标签_搜索完成.文本颜色 = #绿色 标签_搜索完成.标题 = “扫描完成!” .否则 标签_搜索完成.标题 = “正在扫描,剩余目录:” + 到文本 (tmp_count) .如果结束 线程_退出许可区 (UI更新许可证) .判断循环尾 () .子程序 保存结果 .参数 内容, 文本型 线程_进入许可区 (文件写入锁) 结果文件号 = 打开文件 (“文件列表.txt”, #改写, ) ' 改为改写模式 移到文件尾 (结果文件号) 写出文本 (结果文件号, 内容) 关闭文件 (结果文件号) 线程_退出许可区 (文件写入锁) .子程序 _按钮_停止_被单击 停止标志 = 真 .子程序 __启动窗口_将被销毁 停止标志 = 真 .判断循环首 (线程数 > 0) ' 等待所有线程退出 程序_延时 (100) .判断循环尾 () 线程_删除许可证 (许可证) 写到文件 (取运行目录 () + “\地址.ini”, 到字节集 (编辑框_目录.内容)) .子程序 _编辑框_过滤器_放开某键, 逻辑型 .参数 键代码, 整数型 .参数 功能键状态, 整数型 .如果真 (键代码 = #回车键) _按钮_查找文件_被单击 () .如果真结束 .子程序 _列表框1_列表项被选择 .局部变量 文件内容, 文本型 .局部变量 文件号, 整数型 .局部变量 行数组, 文本型, , "0" .局部变量 列数组, 文本型, , "0" .局部变量 i, 整数型 .局部变量 j, 整数型 .局部变量 A, 整数型 超级列表框_数据显示.全部删除 () ' 确定需要加入的位置 文件路径 = 列表框1.取项目文本 (列表框1.现行选中项) ' 关键修复:更新全局路径 文件号 = 打开文件 (文件路径, , ) 文件内容 = 读入文本 (文件号, ) 列数组 = 分割文本 (文件内容, #换行符, ) 关闭文件 (文件号) ' 导入每列数据 .计次循环首 (取数组成员数 (列数组), i) A = 超级列表框_数据显示.插入表项 (, 到文本 (超级列表框_数据显示.取表项数 () + 1), , , , ) ' 应在循环内对每一行列进行分割 .如果真 (取文本长度 (列数组 [i]) > 0) 行数组 = 分割文本 (列数组 [i], “|”, ) ' ? 每行独立分割 .计次循环首 (取数组成员数 (行数组), j) 超级列表框_数据显示.置标题 (A, j, 行数组 [j]) .计次循环尾 () .如果真结束 .计次循环尾 () .子程序 _超级列表框_数据显示_被双击, 逻辑型 .参数 横向位置, 整数型 .参数 纵向位置, 整数型 .参数 功能键状态, 整数型 .局部变量 行索引, 整数型 .局部变量 客户名称, 文本型 .局部变量 颜色, 文本型 .局部变量 数量, 文本型 .局部变量 电压, 文本型 .局部变量 电流, 文本型 .局部变量 老化, 文本型 .局部变量 全检, 文本型 .局部变量 备注, 文本型 ' 获取当前被双击的行索引(从1开始) 行索引 = 超级列表框_数据显示.现行选中项 .如果真 (行索引 ≠ -1) ' 获取该行第一列的内容(可根据需要改为其他列) 客户名称 = 超级列表框_数据显示.取标题 (行索引, 1) 颜色 = 超级列表框_数据显示.取标题 (行索引, 2) 数量 = 超级列表框_数据显示.取标题 (行索引, 3) 电压 = 超级列表框_数据显示.取标题 (行索引, 4) 电流 = 超级列表框_数据显示.取标题 (行索引, 5) 老化 = 超级列表框_数据显示.取标题 (行索引, 6) 全检 = 超级列表框_数据显示.取标题 (行索引, 7) 备注 = 超级列表框_数据显示.取标题 (行索引, 8) 编辑框_客户名称.内容 = 客户名称 编辑框_颜色.内容 = 颜色 编辑框_数量.内容 = 数量 编辑框_电流.内容 = 电流 .判断开始 (老化 = “是”) 组合框_老化.现行选中项 = 0 .判断 (老化 = “否”) 组合框_老化.现行选中项 = 1 .默认 .判断结束 .判断开始 (电压 = “24”) 组合框_电压.现行选中项 = 0 .判断 (电压 = “12”) 组合框_电压.现行选中项 = 1 .判断 (电压 = “48”) 组合框_电压.现行选中项 = 2 .判断 (电压 = “5”) 组合框_电压.现行选中项 = 3 .判断 (电压 = “ 3”) 组合框_电压.现行选中项 = 4 .默认 .判断结束 .判断开始 (全检 = “否”) 组合框_全检.现行选中项 = 0 .判断 (全检 = “是”) 组合框_全检.现行选中项 = 1 .默认 .判断结束 编辑框_备注.内容 = 备注 .如果真结束 .子程序 _按钮_加入数据_被单击 .局部变量 a, 整数型 超级列表框_数据显示.置标题 (a, 4, 电压值) ' 在最后一行插入新的条目 .如果 (编辑框_客户名称.内容 ≠ “” 且 编辑框_颜色.内容 ≠ “” 且 编辑框_数量.内容 ≠ “” 且 编辑框_电流.内容 ≠ “”) a = 超级列表框_数据显示.插入表项 (, 到文本 (超级列表框_数据显示.取表项数 () + 1), , , , ) 超级列表框_数据显示.置标题 (a, 1, 文本_删首尾空 (编辑框_客户名称.内容)) 超级列表框_数据显示.置标题 (a, 2, 文本_删首尾空 (到大写 (编辑框_颜色.内容))) 超级列表框_数据显示.置标题 (a, 3, 文本_删首尾空 (编辑框_数量.内容)) .判断开始 (组合框_电压.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 4, “24”) .判断 (组合框_电压.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 4, “12”) .判断 (组合框_电压.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 4, “48”) .判断 (组合框_电压.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 4, “5”) .判断 (组合框_电压.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 4, “3”) .默认 .判断结束 超级列表框_数据显示.置标题 (a, 5, 文本_删首尾空 (编辑框_电流.内容)) .判断开始 (组合框_老化.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 6, “是”) .默认 组合框_老化.现行选中项 = 1 超级列表框_数据显示.置标题 (a, 6, “否”) .判断结束 .判断开始 (组合框_全检.现行选中项 = 0) 超级列表框_数据显示.置标题 (a, 7, “否”) .默认 组合框_老化.现行选中项 = 1 超级列表框_数据显示.置标题 (a, 7, “是”) .判断结束 超级列表框_数据显示.置标题 (a, 8, 文本_删首尾空 (编辑框_备注.内容)) 超级列表框_数据显示.置标题 (a, 9, 到文本 (取年份 (取现行时间 ())) + “年” + 到文本 (取月份 (取现行时间 ())) + “月” + 到文本 (取日 (取现行时间 ())) + “日”) .否则 信息框 (“输入错误!”, 0, , ) .如果结束 编辑框_客户名称.内容 = “” 编辑框_备注.内容 = “” .子程序 _按钮_删除文件_被单击 .局部变量 行索引, 整数型 .局部变量 被选中的行数组, 整数型, , "0" 被选中的行数组 = 超级列表框_数据显示.取被选择表项 () .如果真 (取数组成员数 (被选中的行数组) > 0) 行索引 = 被选中的行数组 [1] ' 获取第一个被选中的行索引 超级列表框_数据显示.删除表项 (行索引) .如果真结束 .子程序 _按钮_保存文件_被单击 .局部变量 保存到的路径, 文本型 .局部变量 对话框结果, 逻辑型 .如果真 (超级列表框_数据显示.取表项数 () = 0) 信息框 (“没有需要保存的内容!”, 0, , ) 返回 () .如果真结束 ' 若当前已有文件路径,则提取目录部分作为初始目录 .如果真 (取反 (文件路径 = “”)) 保存到的路径 = 文本_取左边 (文件路径, “\”, -1, ) 通用对话框_保存.初始目录 = 保存到的路径 通用对话框_保存.文件名 = 编辑框_过滤器.内容 .如果真结束 ' 设置保存对话框属性 通用对话框_保存.类型 = 1 ' 1 表示保存文件 通用对话框_保存.过滤器 = “文本文件(*.txt)|*.txt” ' 通用对话框_保存.默认扩展名 = “txt” ' 显示对话框 对话框结果 = 通用对话框_保存.打开 () .如果真 (对话框结果) ' 用户点击“保存”后执行保存操作 文件路径 = 通用对话框_保存.文件名 ' 更新全局路径 超级列表框_保存到文件 (文件路径, 假) ' 假:覆盖写入 ' 信息框 (“文件已成功保存!”, 0, , ) .如果真结束 .子程序 超级列表框_保存到文件 .参数 目标路径, 文本型 .参数 是否追加, 逻辑型 .局部变量 文件号, 整数型 .局部变量 行索引, 整数型 .局部变量 列索引, 整数型 .局部变量 当前行文本, 文本型 ' 打开文件(追加或新建) .如果 (是否追加) 文件号 = 打开文件 (目标路径, #改写, ) ' 覆盖模式 .否则 文件号 = 打开文件 (目标路径, #重写, ) ' 创建模式 .如果结束 .如果真 (文件号 = 0) 信息框 (“文件创建失败!”, 0, , ) 返回 () .如果真结束 ' 写入数据行 .计次循环首 (超级列表框_数据显示.取表项数 (), 行索引) 当前行文本 = “” .计次循环首 (超级列表框_数据显示.取列数 (), 列索引) 当前行文本 = 当前行文本 + 超级列表框_数据显示.取标题 (行索引 - 1, 列索引) + “|” .计次循环尾 () 写文本行 (文件号, 当前行文本) .计次循环尾 () 关闭文件 (文件号)
最新发布
11-10
<think>我们面对的是一个使用易语言(EPL)编写的多线程文件扫描程序。从代码中可以看出,这个程序的主要功能是扫描指定目录下的所有子目录,并查找符合过滤器条件的文件(这里过滤器后面加了".txt",但代码中可能应该是没有后缀的,需要根据实际需求调整)。扫描到的文件会显示在列表框中,同时将文件路径保存到"文件列表.txt"中。此外,程序还包含一个超级列表框用于显示文件内容,并支持编辑保存。 在分析过程中,我发现了一些问题: 1. 在`目录扫描线程`子程序中,文件枚举的调用是: `文件_枚举 (当前目录, 编辑框_过滤器.内容 + “.txt”, 文件数组, 真, 假, 真)` 这里将过滤器内容后面都加上了".txt",这意味着只能查找.txt文件。但用户输入的过滤器可能是多种后缀,或者没有后缀。所以这里应该根据用户输入来,而不是固定添加".txt"。 2. 在`_按钮_查找文件_被单击`事件中,启动线程的循环是: `.计次循环首 (最大线程数, )` 启动线程 (&目录扫描线程, , ) `.计次循环尾 ()` 但是,在窗口创建完毕时已经启动了最大线程数个线程(线程池),在点击查找按钮时又启动了一次,这样会导致线程重复启动。应该避免重复启动线程池。 3. 在`目录扫描线程`中,更新UI标签(标签_搜索完成)的代码位于线程的循环体内,且每次处理一个目录后都会更新。但是,这个更新操作是在一个循环内频繁进行的,而且使用了许可证来保护。虽然安全,但可能影响性能,且更新频率过高。 4. 在`_按钮_加入数据_被单击`事件中,为超级列表框加入数据时,电压组合框的判断有误,所有分支都判断了现行选中项为0,导致只有第一个分支有效。 5. 在`_按钮_保存文件_被单击`事件中,调用`超级列表框_保存到文件`子程序时,传递了假(覆盖写入),但是子程序内部却根据参数`是否追加`使用了不同的打开方式。注意:子程序中打开文件的方式有误,覆盖写入应该使用`#重写`,追加写入应该使用`#改写`(在文件尾追加)。但是子程序中的逻辑是: - 如果“是否追加”为真,则用`#改写`(覆盖写入?注意:易语言中`#改写`是覆盖写入,`#追加`才是追加写入) - 否则用`#重写`(创建新文件) 实际上,子程序中参数“是否追加”的含义是:是否追加到已有文件。 但是,在子程序中,当“是否追加”为真时,使用的是改写(覆盖)模式,这会导致原有内容被覆盖,而不是追加。应该改为: .如果 (是否追加) 文件号 = 打开文件 (目标路径, #改写, ) -> 改为 #追加 .否则 文件号 = 打开文件 (目标路径, #重写, ) .如果结束 6. 在`超级列表框_保存到文件`子程序中,写入文件时,每一行都加了换行符,但每列之间用“|”分隔,这个格式之前读取的格式一致。但是在写入时,每一行末尾会多出一个“|”,因为每列循环都加了一个分隔符。这可能导致读取时最后一列为空。 7. 在`目录扫描线程`中,当从目录队列取目录时,使用了一个全局的`工作线程数`来记录正在工作的线程数,但是当队列为空时,线程会休眠,但是并没有减少工作线程数,直到找到目录并处理完子目录后才减少。这样设计会导致在队列为空时,工作线程数并没有减少,从而可能使标签显示一直处于“正在扫描”状态。 8. 在`__启动窗口_将被销毁`事件中,有等待线程退出的循环,但使用的是`线程数`变量,而代码中并没有维护`线程数`(即启动线程时没有增加,结束时没有减少)。所以这个等待循环可能无效。 9. 在`目录扫描线程`中,当扫描到一个目录下的文件时,使用了一个字符串`缓存路径`来累积文件路径,然后批量写入文件。但是,如果文件很多,这个字符串可能会非常大,占用大量内存。可以考虑每100个文件写入一次,或者每次扫描到一个文件就写入(但这样频繁写入会影响性能,折中办法是使用缓冲区,比如每10个文件写一次)。 10. 在`_列表框1_列表项被选择`事件中,读取选中的文件内容并显示在超级列表框中。这里读取文件后,按行分割,然后每一行再按“|”分割。注意,如果文件内容不是按“|”分隔的,就会出错。而且,这个事件中并没有处理文件读取失败的情况。 11. 在`_按钮_删除文件_被单击`事件中,只能删除超级列表框中被选中的第一行。如果要删除多行,需要支持多选删除。 12. 在界面部分,有一个标签_搜索完成,用于显示扫描状态。但是,在多线程更新UI时,使用了UI更新许可证,这是正确的。 由于代码较长,我们不可能全部改写。下面针对主要问题提出修正: 问题1修正:文件枚举时,应该直接使用编辑框_过滤器.内容,而不加".txt"。因为用户可能输入多个后缀,比如"*.txt;*.log"。 将 `文件_枚举 (当前目录, 编辑框_过滤器.内容 + “.txt”, 文件数组, 真, 假, 真)` 改为 `文件_枚举 (当前目录, 编辑框_过滤器.内容, 文件数组, 真, 假, 真)` 问题2修正:避免重复启动线程。在窗口创建完毕时已经启动了线程池(最大线程数个线程),这些线程会一直运行(通过循环判断停止标志)。所以在点击查找按钮时,只需要重置停止标志,并初始化目录队列,然后唤醒这些线程(通过往队列里加入初始目录)即可,不需要再启动线程。 问题4修正:在`_按钮_加入数据_被单击`中,电压组合框的判断错误,应该根据组合框的选中项来分别设置: 修改如下: ``` .判断开始 (组合框_电压.现行选中项 = 0) 电压值 = “24” .判断 (组合框_电压.现行选中项 = 1) 电压值 = “12” .判断 (组合框_电压.现行选中项 = 2) 电压值 = “48” .判断 (组合框_电压.现行选中项 = 3) 电压值 = “5” .判断 (组合框_电压.现行选中项 = 4) 电压值 = “3” .默认 电压值 = “” .判断结束 超级列表框_数据显示.置标题 (a, 4, 电压值) ``` 问题5修正:保存文件时,如果选择追加,应该用追加模式(#追加)打开文件。修改`超级列表框_保存到文件`子程序: ``` .如果 (是否追加) 文件号 = 打开文件 (目标路径, #追加, ) .否则 文件号 = 打开文件 (目标路径, #重写, ) .如果结束 ``` 问题6修正:在保存超级列表框内容时,每一行最后一列后面不要加“|”。可以在内层循环中判断是否是最后一列,如果不是则加“|”,否则不加。或者在内层循环后去掉最后一个“|”。这里选择在内层循环中处理: ``` .计次循环首 (超级列表框_数据显示.取列数 (), 列索引) 当前行文本 = 当前行文本 + 超级列表框_数据显示.取标题 (行索引 - 1, 列索引) .如果真 (列索引 < 超级列表框_数据显示.取列数 ()) 当前行文本 = 当前行文本 + “|” .如果真结束 .计次循环尾 () ``` 问题7修正:在`目录扫描线程`中,工作线程数的增减逻辑需要调整。目前是: 从队列取到一个目录,工作线程数+1,处理完该目录(包括枚举文件子目录)后,工作线程数-1。 但是,当队列为空时,线程会休眠(到循环尾),此时并没有执行工作线程数的减少。所以,当所有目录都处理完毕,队列为空,但可能还有线程处于休眠状态(工作线程数没有减少)。因此,在队列为空时,应该让线程退出(或者不增加工作线程数)。建议重新设计工作线程数的计数方式。 实际上,我们可以认为,当线程从队列中取出一个目录开始处理时,该线程处于工作状态;处理完该目录并加入新的子目录后,该线程就完成了一次工作,然后工作线程数减1。这样,当队列为空时,所有线程都在休眠(工作线程数不会增加),所以此时工作线程数应该为0。但是,在进入循环前我们并没有工作,所以初始工作线程数为0。 但是,在标签更新代码中,我们使用了`tmp_count`(队列中目录数)`tmp_workers`(当前工作线程数)来判断是否完成。当队列为空且工作线程数为0时,才显示完成。 然而,在队列为空时,线程休眠并不会减少工作线程数,所以工作线程数可能不为0。因此,我们需要调整:当线程因为队列为空而休眠时,它不处于工作状态,所以不应该被计入工作线程数。 修改思路:将工作线程数的增加减少放在真正处理目录的时候。也就是说,当线程从队列中取到目录时才增加,处理完该目录后立即减少。这样,当队列为空时,所有线程都处于等待状态,工作线程数为0。 因此,当前代码中工作线程数的增减位置是合适的。但是,在标签更新代码中,我们使用了临时变量`tmp_workers`,它是在从队列取出目录后(工作线程数已经增加)加入子目录后(工作线程数减少)之间的值。因此,在标签更新时,应该使用一个全局的队列目录数工作线程数。 不过,在更新标签时,我们使用了临界区保护,所以获取到的`tmp_count``tmp_workers`是临时的快照。注意:在减少工作线程数后,我们才获取这两个值。 所以,当处理完一个目录(工作线程数减少)后,如果此时队列为空且工作线程数减少后变为0,那么就会显示完成。但是,有可能其他线程在此时又加入了新的目录?所以,需要确保在临界区内完成判断。 实际上,在标签更新代码中,我们是在临界区外更新UI(为了减少锁的占用时间),所以获取到的值可能已经变化。因此,可能会出现这样的情况:当最后一个工作线程处理完目录,减少工作线程数(变为0)并且此时队列也为空,那么就会显示完成。但是,如果在显示完成之前,另一个线程又加入了目录(不太可能,因为加入目录的操作也是在临界区内,并且该线程已经处理完了,不会再有其他线程同时加入?),那么显示就不准确。 所以,这个设计基本可行,但需要确保在临界区内获取状态,然后快速更新。我们目前的做法是:在临界区内拷贝状态到临时变量,然后在临界区外更新UI。这样,即使状态在拷贝后发生了变化,UI更新的也是之前的状态,但不会造成程序崩溃。 因此,问题7的修正主要是确保工作线程数的计数正确:只有线程在处理目录时才计数。当前代码已经这样做了,所以不用修改计数部分。 问题8修正:在窗口销毁时,需要等待所有线程退出。但是,代码中没有维护线程数(启动线程时没有增加全局计数器,线程结束时也没有减少)。所以,我们需要一个线程计数器,并在启动线程时增加,线程退出时减少。 修改: 在程序集变量中增加:`线程数`(已有) 在启动线程的地方(窗口创建完毕时): .计次循环首 (最大线程数, ) 启动线程 (&目录扫描线程, , ) 线程数 = 线程数 + 1 .计次循环尾 () 在线程函数(目录扫描线程)的末尾(即整个线程执行完毕后): 线程数 = 线程数 - 1 然后,在窗口销毁事件中: 停止标志 = 真 .判断循环首 (线程数 > 0) 程序_延时 (100) .判断循环尾 () 但是注意,线程函数是一个循环,除非停止标志为真,否则不会退出。所以,我们在窗口销毁时设置停止标志,然后等待线程计数变为0。 问题9:文件写入的缓冲问题。为了减少文件写入次数,我们使用了缓存路径,每次处理一个目录下的所有文件,然后一次性写入。但如果一个目录下有成千上万个文件,缓存字符串会很大。我们可以设置一个计数器,每100个文件写入一次。 不过,考虑到性能平衡,当前代码在单个目录下文件数量通常不会太多,可以暂时不修改。如果确实遇到大目录,再考虑分批次写入。 由于时间限制,其他问题(如问题11多选删除)可能比较复杂,这里暂不处理。 下面针对上述修正点,给出修改后的关键代码片段: 注意:由于易语言语法特殊,以下代码仅供参考,实际修改请根据上下文调整。 修改1:文件枚举 ```epl 文件_枚举 (当前目录, 编辑框_过滤器.内容, 文件数组, 真, 假, 真) ``` 修改2:避免重复启动线程(在窗口创建完毕时已经启动线程池),所以在`_按钮_查找文件_被单击`事件中,不再启动线程,而是重置队列停止标志,并加入初始目录。同时,需要唤醒线程(因为线程可能处于休眠状态)。但是,线程在循环中会不断检查队列,所以只需要加入初始目录,线程就会自动处理。 因此,删除`_按钮_查找文件_被单击`事件中的启动线程的循环。 修改4:电压组合框判断 ```epl .判断开始 (组合框_电压.现行选中项 = 0) 电压值 = “24” .判断 (组合框_电压.现行选中项 = 1) 电压值 = “12” .判断 (组合框_电压.现行选中项 = 2) 电压值 = “48” .判断 (组合框_电压.现行选中项 = 3) 电压值 = “5” .判断 (组合框_电压.现行选中项 = 4) 电压值 = “3” .默认 电压值 = “” .判断结束 超级列表框_数据显示.置标题 (a, 4, 电压值) ``` 修改5:保存文件时追加写入 ```epl .子程序 超级列表框_保存到文件 .参数 目标路径, 文本型 .参数 是否追加, 逻辑型 .局部变量 文件号, 整数型 .局部变量 行索引, 整数型 .局部变量 列索引, 整数型 .局部变量 当前行文本, 文本型 ' 打开文件(追加或新建) .如果 (是否追加) 文件号 = 打开文件 (目标路径, #追加, ) ' 追加模式 .否则 文件号 = 打开文件 (目标路径, #重写, ) ' 覆盖模式 .如果结束 ... 后面不变 ... ``` 修改6:保存文件时每行内列之间用“|”分隔,但最后一列后不加 ```epl .计次循环首 (超级列表框_数据显示.取列数 (), 列索引) 当前行文本 = 当前行文本 + 超级列表框_数据显示.取标题 (行索引 - 1, 列索引) .如果真 (列索引 < 超级列表框_数据显示.取列数 ()) 当前行文本 = 当前行文本 + “|” .如果真结束 .计次循环尾 () ``` 修改8:维护线程数 在窗口程序集变量中,已有线程数变量,在窗口创建完毕时: ```epl .计次循环首 (最大线程数, ) 启动线程 (&目录扫描线程, , ) 线程数 = 线程数 + 1 .计次循环尾 () ``` 在目录扫描线程的末尾(即整个子程序的结束处): ```epl 线程数 = 线程数 - 1 ``` 在窗口销毁事件中: ```epl 停止标志 = 真 .判断循环首 (线程数 > 0) ' 等待所有线程退出 程序_延时 (100) .判断循环尾 () ``` 注意:由于线程函数是一个循环,在停止标志为真时,线程会退出循环并执行到子程序末尾(线程数减1),然后线程结束。 由于修改点较多,请仔细测试。 由于代码量很大,这里只给出关键修改点的代码片段,无法给出完整代码。 最后,给出一些建议: 1. 对于多线程程序,调试起来比较困难,建议添加日志功能,记录线程的运行状态。 2. 在使用许可证时,注意避免死锁(比如在一个许可证内又去申请另一个许可证)。 3. 考虑使用更高级的线程池管理。 以上是针对现有代码的修正建议。 </think>根据您提供的易语言代码,我分析了文件扫描多线程处理的实现,并发现几个需要改进的关键问题: ### 主要问题及解决方案 1. **线程管理问题** - 在`__启动窗口_创建完毕``_按钮_查找文件_被单击`中都启动了线程池 - 会导致线程数量倍增,消耗系统资源 ```epl ;; 修正方案:只在窗口创建时启动一次线程池 .子程序 __启动窗口_创建完毕 ;; ...其他代码... 最大线程数 = 5 .计次循环首 (最大线程数, ) 启动线程 (&目录扫描线程, , ) .计次循环尾 () .子程序 _按钮_查找文件_被单击 ;; 删除这里的线程启动代码 ;; 只需重置队列即可 清除数组 (目录队列) 加入成员 (目录队列, 初始目录) ``` 2. **文件枚举过滤错误** - 当前代码强制添加`.txt`后缀,导致只能搜索文本文件 ```epl ;; 修正方案:使用原始过滤器 .子程序 目录扫描线程 ;; 修正前:文件_枚举 (当前目录, 编辑框_过滤器.内容 + ".txt", 文件数组, 真, 假, 真) 文件_枚举 (当前目录, 编辑框_过滤器.内容, 文件数组, 真, 假, 真) ;; 去掉强制.txt后缀 ``` 3. **电压值设置错误** - 所有分支都判断组合框_电压.现行选中项=0 ```epl ;; 修正方案:正确判断选中项 .子程序 _按钮_加入数据_被单击 .判断开始 (组合框_电压.现行选中项 = 0) 电压值 = "24" .判断 (组合框_电压.现行选中项 = 1) ;; 新增分支 电压值 = "12" .判断 (组合框_电压.现行选中项 = 2) ;; 新增分支 电压值 = "48" .判断 (组合框_电压.现行选中项 = 3) ;; 新增分支 电压值 = "5" .判断 (组合框_电压.现行选中项 = 4) ;; 新增分支 电压值 = "3" .默认 电压值 = "" .判断结束 超级列表框_数据显示.置标题 (a, 4, 电压值) ;; 使用变量 ``` 4. **文件保存格式问题** - 每行末尾会多出多余的管道符"|" ```epl ;; 修正方案:处理末尾分隔符 .子程序 超级列表框_保存到文件 .计次循环首 (超级列表框_数据显示.取表项数 (), 行索引) 当前行文本 = "" .计次循环首 (超级列表框_数据显示.取列数 (), 列索引) 当前行文本 = 当前行文本 + 超级列表框_数据显示.取标题 (行索引 - 1, 列索引) .如果真 (列索引 < 超级列表框_数据显示.取列数 ()) 当前行文本 = 当前行文本 + "|" ;; 只在列间加分隔符 .如果真结束 .计次循环尾 () 写文本行 (文件号, 当前行文本) .计次循环尾 () ``` 5. **内存优化** - 文件路径缓存可能过大 ```epl ;; 修正方案:限制缓存大小 .子程序 目录扫描线程 .局部变量 文件计数, 整数型 文件计数 = 0 .计次循环首 (取数组成员数 (文件数组), i) ;; ...其他代码... 缓存路径 = 缓存路径 + 文件数组 [i] + #换行符 文件计数 = 文件计数 + 1 .如果真 (文件计数 >= 100) ;; 每100个文件保存一次 保存结果 (缓存路径) 缓存路径 = "" 文件计数 = 0 .如果真结束 .计次循环尾 () ``` ### 完整改进建议 1. **线程池管理** - 使用单一初始化点 - 添加线程计数器 ```epl .程序集变量 线程句柄, 整数型, , "0" ;; 存储线程句柄 .子程序 __启动窗口_创建完毕 重定义数组 (线程句柄, 假, 最大线程数) .计次循环首 (最大线程数, i) 线程句柄[i] = 启动线程 (&目录扫描线程, i, ) .计次循环尾 () ``` 2. **队列管理优化** ```epl .子程序 获取下一个目录, 文本型 线程_进入许可区 (队列许可证) .如果 (取数组成员数 (目录队列) > 0) 当前目录 = 目录队列[1] 删除成员 (目录队列, 1, 1) .否则 当前目录 = "" .如果结束 线程_退出许可区 (队列许可证) 返回 (当前目录) ``` 3. **文件写入优化** ```epl .子程序 保存结果 .参数 内容, 文本型 线程_进入许可区 (文件写入锁) 结果文件号 = 打开文件 ("文件列表.txt", #改写, ) .如果真 (结果文件号 ≠ 0) 移到文件尾 (结果文件号) 写出文本 (结果文件号, 内容) 关闭文件 (结果文件号) .如果真结束 线程_退出许可区 (文件写入锁) ``` ### 文件扫描流程优化 ```mermaid graph TD A[开始扫描] --> B{队列有目录?} B -->|是| C[取目录] B -->|否| D[等待100ms] C --> E[枚举文件] E --> F[更新UI] F --> G[保存到缓存] E --> H[枚举子目录] H --> I[子目录入队] I --> J[更新状态] J --> B D --> B ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值