循环创建子线程时访问主线程变量出现异常结果
多线程处理数据的变量捕获异常问题及修复
我正在编写一个读取大量动态数据的程序,希望通过多线程加速处理过程。了解线程调度由操作系统负责,但出现了异常结果,不知道解决方案。
原代码
int multiplier = rowCount / 20; Debug.WriteLine("Row count is " + rowCount + "..."); Debug.WriteLine("Using 20 threads to complete job..."); Debug.WriteLine("Using " + multiplier + " as multiplier..."); for (int i = 1; i <= 20; i++) { new Thread(() => { int startRow = ((multiplier * i) - multiplier) + 1; int endRow = multiplier * i; if (i == 20) { endRow = rowCount; } Debug.WriteLine(" [THREAD " + i + "] start row: " + startRow + ", end row : " + endRow); for (int row = startRow; row <= endRow; row++) { for (int column = 1; column <= columnCount; column++) { //...data is read here } } }).Start(); }
实际异常结果
Row count is 2209... Using 20 threads to complete job... Using 110 as multiplier... [THREAD 4] start row: 331, end row : 440 [THREAD 4] start row: 331, end row : 440 [THREAD 5] start row: 441, end row : 550 [THREAD 5] start row: 441, end row : 550 [THREAD 6] start row: 551, end row : 660 [THREAD 7] start row: 661, end row : 770 [THREAD 9] start row: 881, end row : 990 [THREAD 9] start row: 881, end row : 990 [THREAD 11] start row: 1101, end row : 1210 [THREAD 11] start row: 1101, end row : 1210 [THREAD 12] start row: 1211, end row : 1320 [THREAD 14] start row: 1431, end row : 1540 [THREAD 15] start row: 1541, end row : 1650 [THREAD 15] start row: 1541, end row : 1650 [THREAD 16] start row: 1651, end row : 1760 [THREAD 17] start row: 1761, end row : 1870 [THREAD 19] start row: 1981, end row : 2090 [THREAD 20] start row: 2091, end row : 2209 [THREAD 20] start row: 2091, end row : 2209 [THREAD 21] start row: 2201, end row : 2310
预期结果
Row count is 2209... Using 20 threads to complete job... Using 110 as multiplier... [THREAD 1] start row: 1, end row : 110 [THREAD 2] start row: 111, end row : 220 [THREAD 3] start row: 221, end row : 330 [THREAD 4] start row: 331, end row : 440 [THREAD 5] start row: 441, end row : 550 [THREAD 6] start row: 551, end row : 660 [THREAD 7] start row: 661, end row : 770 [THREAD 8] start row: 771, end row : 880 [THREAD 9] start row: 881, end row : 990 [THREAD 10] start row: 991, end row : 1100 [THREAD 11] start row: 1101, end row : 1210 [THREAD 12] start row: 1211, end row : 1320 [THREAD 13] start row: 1321, end row : 1430 [THREAD 14] start row: 1431, end row : 1540 [THREAD 15] start row: 1541, end row : 1650 [THREAD 16] start row: 1651, end row : 1760 [THREAD 17] start row: 1761, end row : 1870 [THREAD 18] start row: 1871, end row : 1980 [THREAD 19] start row: 1981, end row : 2090 [THREAD 20] start row: 2091, end row : 2209
问题原因及修复方案
问题根源
这是闭包捕获循环变量的经典问题:lambda表达式捕获的是循环变量i的引用,而非当前迭代的具体值。主线程循环执行速度远快于子线程启动,当子线程开始运行时,i已经被后续迭代修改,甚至循环结束后i会变成21(因为循环条件是i<=20,最后一次迭代后i会自增到21),这就导致了重复线程编号、缺失编号甚至出现THREAD 21的异常情况。
修复方法
在循环内部创建一个局部变量,保存当前迭代的i值,让lambda捕获这个局部变量的副本(而非循环变量的引用)。这样每个线程拿到的都是独立、固定的值,不会被后续循环修改。
修改后的代码
int multiplier = rowCount / 20; Debug.WriteLine("Row count is " + rowCount + "..."); Debug.WriteLine("Using 20 threads to complete job..."); Debug.WriteLine("Using " + multiplier + " as multiplier..."); for (int i = 1; i <= 20; i++) { // 用局部变量保存当前迭代的i值 int currentThreadNum = i; new Thread(() => { int startRow = ((multiplier * currentThreadNum) - multiplier) + 1; int endRow = multiplier * currentThreadNum; if (currentThreadNum == 20) { endRow = rowCount; } Debug.WriteLine(" [THREAD " + currentThreadNum + "] start row: " + startRow + ", end row : " + endRow); for (int row = startRow; row <= endRow; row++) { for (int column = 1; column <= columnCount; column++) { //...data is read here } } }).Start(); }
运行修改后的代码,就能得到预期的线程分配结果,每个线程处理独立的行区间,不会出现重复或缺失的情况。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

