索引溢出在带有嵌套线程的for循环中。

huangapple go评论136阅读模式
英文:

Index overflow in for loop with nested thread

问题

我正在编写一个测试工具(一个WinForm),以检查一个网站的性能。为此,我设置了要执行的请求数量,并附带了一组与请求相关的参数列表。我可以将请求设置为并行执行或按顺序执行。

如果按顺序执行一切正常,但如果并行执行,我会遇到一个与for循环相关的奇怪问题。
我知道我可以使用Parallel.For,但因为我正在调查与并行性相关的另一个错误,所以我临时使用了一个常规的for循环,其中包含直接执行或使用Task.Run()的嵌套操作。

以下是有问题的代码:

  1. private void Run()
  2. {
  3. ConcurrentBag<long> callTimes = new ConcurrentBag<long>();
  4. int httpErrors = 0;
  5. int progress = 0;
  6. string uri = txtUrl.Text ?? string.Empty;
  7. if (string.IsNullOrWhiteSpace(uri))
  8. return;
  9. Func<List<string>, int, long> testCall = (p, i) =>
  10. {
  11. try
  12. {
  13. using (var client = new HttpClient())
  14. {
  15. Stopwatch timer = new Stopwatch();
  16. timer.Start();
  17. string actualUrl = string.Format(uri, p.ToArray());
  18. var getTask = client.GetAsync(actualUrl);
  19. getTask.Wait();
  20. timer.Stop();
  21. var result = getTask.Result;
  22. if (result == null || (int)result.StatusCode >= 400)
  23. {
  24. txtErrors.ThreadSafeAppendText($"Connection error {(result?.StatusCode.ToString() ?? "NULL")}'\r\n");
  25. Interlocked.Increment(ref httpErrors);
  26. }
  27. return timer.ElapsedMilliseconds;
  28. }
  29. }
  30. catch (Exception actionErr)
  31. {
  32. txtErrors.ThreadSafeAppendText($"Error while execution callAction {i} with parameters '{string.Join(", ", p)}' : \r\n" + actionErr.Message);
  33. }
  34. return -1;
  35. };
  36. try
  37. {
  38. List<List<string>> parameters = this.ParseParameters();
  39. int parametersCount = parameters.Count;
  40. int executions = (int)updRequests.Value;
  41. //used to randomly access parameters in a way suitable also for the parallel scenario (i precompute all the random number i need while parallel processing is not yet started)
  42. Random rng = new Random();
  43. List<int> randoms = new List<int>();
  44. for (int i = 0; i < executions; i++)
  45. randoms.Add(rng.Next(0, parametersCount));
  46. //randoms.Count is guaranteed to be equal to executions
  47. for (int index = 0; index < executions; index++)
  48. {
  49. Action parallelAction = () =>
  50. {
  51. int currentIndex = index;
  52. List<string> currentParameter = parameters[randoms[currentIndex] % parametersCount]; //<<--- strange overflow here currentIndex >= executions
  53. callTimes.Add(testCall(currentParameter, currentIndex));
  54. Interlocked.Increment(ref progress);
  55. if (progress % 10 == 0)
  56. prbProgress.ThreadSafeAction(this.RefreshProgressBar, progress, executions);
  57. };
  58. if (chkParallelExecution.Checked)
  59. Task.Run(parallelAction);
  60. else
  61. parallelAction();
  62. }
  63. this.Reporting(callTimes, httpErrors);
  64. }
  65. catch (Exception err)
  66. {
  67. txtErrors.ThreadSafeAppendText($"Error while running stress test : \r\n" + err.Message);
  68. }
  69. }

我不理解的奇怪之处是,变量currentIndex如何变得大于或等于executions,因为只有循环操作这两个变量,应该强制执行相反的情况。所以我认为我在理解并行处理的方式方面可能有所遗漏。

英文:

I am writing a testing utility (a WinForm) to check how a web site perform. To do so i set a number of request to make, with a list of parameters associated with requests. I can set the requests to happen in parallel or in sequence.

If i work in sequence everything is fine, but if i work in parallel i get a strange issue with the for loop.
I know i may use Parallel.For but because i am investigating another bug related to parallelism i temporary used a regualr for, with a nested action executed directly or with a Task.Run().

Here the problematic code :

  1. private void Run()
  2. {
  3. ConcurrentBag&lt;long&gt; callTimes = new ConcurrentBag&lt;long&gt;();
  4. int httpErrors = 0;
  5. int progress = 0;
  6. string uri = txtUrl.Text ?? string.Empty;
  7. if (string.IsNullOrWhiteSpace(uri))
  8. return;
  9. Func&lt;List&lt;string&gt;,int,long&gt; testCall = (p,i) =&gt;
  10. {
  11. try
  12. {
  13. using (var client = new HttpClient())
  14. {
  15. Stopwatch timer = new Stopwatch();
  16. timer.Start();
  17. string actualUrl = string.Format(uri, p.ToArray());
  18. var getTask = client.GetAsync(actualUrl);
  19. getTask.Wait();
  20. timer.Stop();
  21. var result = getTask.Result;
  22. if (result == null || (int)result.StatusCode &gt;= 400)
  23. {
  24. txtErrors.ThreadSafeAppendText($&quot;Connection error {(result?.StatusCode.ToString() ?? &quot;NULL&quot;)}&#39;\r\n&quot;);
  25. Interlocked.Increment(ref httpErrors);
  26. }
  27. return timer.ElapsedMilliseconds;
  28. }
  29. }
  30. catch ( Exception actionErr)
  31. {
  32. txtErrors.ThreadSafeAppendText($&quot;Error while execution callAction {i} with parameters &#39;{string.Join(&quot;, &quot; , p)}&#39; : \r\n&quot; + actionErr.Message);
  33. }
  34. return -1;
  35. };
  36. try
  37. {
  38. List&lt;List&lt;string&gt;&gt; parameters = this.ParseParameters();
  39. int parametersCount = parameters.Count;
  40. int executions = (int)updRequests.Value;
  41. //used to randomly access parameters in a way suitable also for the parallel scenario (i precompute all the random number i need while parallel processing is not yet started)
  42. Random rng = new Random();
  43. List&lt;int&gt; randoms = new List&lt;int&gt;();
  44. for (int i = 0; i &lt; executions; i++)
  45. randoms.Add(rng.Next(0, parametersCount));
  46. //randoms.Count is guaranteed to be equal to executions
  47. for ( int index = 0; index &lt; executions; index++)
  48. {
  49. Action parallelAction = () =&gt;
  50. {
  51. int currentIndex = index;
  52. List&lt;string&gt; currentParameter = parameters[randoms[currentIndex] % parametersCount]; //&lt;&lt;--- strange overflow here currentIndex &gt;= executions
  53. callTimes.Add(testCall(currentParameter, currentIndex));
  54. Interlocked.Increment(ref progress);
  55. if (progress % 10 == 0)
  56. prbProgress.ThreadSafeAction(this.RefreshProgressBar, progress, executions);
  57. };
  58. if (chkParallelExecution.Checked)
  59. Task.Run(parallelAction);
  60. else
  61. parallelAction();
  62. }
  63. this.Reporting(callTimes, httpErrors);
  64. }
  65. catch (Exception err)
  66. {
  67. txtErrors.ThreadSafeAppendText($&quot;Error while running stress test : \r\n&quot; + err.Message);
  68. }
  69. }

The strange thing i don't understand is how the variable called currentIndex become >= executions variable, because only the loop manipulate those two variable and should enforce the opposite.
So i think i am missing something in my understanding on how parallel processing happen here.

答案1

得分: 1

有一个相当知名的问题与capturing loop variables有关。

所以你可能应该这样写

  1. for (int index = 0; index < executions; index++)
  2. {
  3. int currentIndex = index;
  4. Action parallelAction = () =>
  5. {
  6. ...

另一个可能的问题:

  1. parameters[randoms[currentIndex] % parametersCount];

你永远不知道randoms中的值都可能是零。你确定不想创建一个0..executions的数组,然后进行shuffle吗?

但是我无法看到这个示例应该失败的明显原因。但是如果你进行一些调试,与索引等相关的错误应该是相当明显的。

英文:

There is a fairly well known issue with capturing loop variables.

So you should probably write

  1. for ( int index = 0; index &lt; executions; index++)
  2. {
  3. int currentIndex = index;
  4. Action parallelAction = () =&gt;
  5. {
  6. ...

Another possible issue:

  1. parameters[randoms[currentIndex] % parametersCount];

For all you know the values in randoms could all be zero. Are you sure you don't want to create an array of 0..executions, and shuffle this instead?

I can't see any obvious reasons why this example should fail however. But errors with indices etc should be rather obvious if you do some debugging.

huangapple
  • 本文由 发表于 2023年8月10日 17:44:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76874536.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定