Parallel.Foreach 并且每个都产生不同的结果:为什么我的代码不安全?
Parallel.Foreach and for each producing different results: Why is my code unsafe?
我有一个文本文件,我将其读取为一个字符串 content
。为了识别我想进一步处理的文本主体,我获取了字符串中关键字的索引,然后将“起始”索引设置为找到的最小索引。
我试过 Parallel.ForEach
...
ConcurrentBag<int> indexes = new();
int index;
switch (Case)
{
case 1:
Parallel.ForEach(KeywordTypes.GetImplementedNamedObjects(), inos =>
{
index = content.IndexOf($"/begin {inos}");
index = index == -1 ? content.Length : index;
indexes.Add(index);
});
index = indexes.Min();
return index;
... 以及 foreach
:
foreach (string inos in KeywordTypes.GetImplementedNamedObjects())
{
index = content.IndexOf($"/begin {inos}");
index = index == -1 ? content.Length : index;
indexes.Add(index);
}
index = indexes.Min();
return index;
其中 foreach
产生了预期的结果,但 Parallel.ForEach
没有。
为什么我的代码不是线程安全的?
这里只有一个index
变量,因为它被“捕获”了。这意味着多个线程可以为它争吵,而不是每个线程都有自己的版本。
考虑:
- 线程 A 计算
index = content.IndexOf($"/begin {inos}");
- 线程 B 计算
index = content.IndexOf($"/begin {inos}");
- 糟糕,线程 A 的版本刚刚被覆盖
- 线程 A 使用 B 刚刚更新的
index
计算 index = index == -1 ? content.Length : index;
- 等等
要点是:由于线程争用而丢失了一个值。
只需移动 index
的声明即可解决此问题:
Parallel.ForEach(KeywordTypes.GetImplementedNamedObjects(), inos =>
{
var index = content.IndexOf($"/begin {inos}");
...
从根本上说,变量的作用域是由声明它的地方定义的。如果在局部方法/lambda 的 外部 声明变量,编译器会尊重您的要求,并且该变量在该局部方法/lambda 的所有使用之间共享;如果它在 内部 本地方法/lambda 中声明,则生命周期是该调用的本地,调用者之间不共享任何状态。
如果你想绝对确定你没有意外泄漏状态,lambda 上的 static
修饰符可以实现这一点,尽管它也可以防止 indexes
被访问,所以......可能这不是你需要的。
我有一个文本文件,我将其读取为一个字符串 content
。为了识别我想进一步处理的文本主体,我获取了字符串中关键字的索引,然后将“起始”索引设置为找到的最小索引。
我试过 Parallel.ForEach
...
ConcurrentBag<int> indexes = new();
int index;
switch (Case)
{
case 1:
Parallel.ForEach(KeywordTypes.GetImplementedNamedObjects(), inos =>
{
index = content.IndexOf($"/begin {inos}");
index = index == -1 ? content.Length : index;
indexes.Add(index);
});
index = indexes.Min();
return index;
... 以及 foreach
:
foreach (string inos in KeywordTypes.GetImplementedNamedObjects())
{
index = content.IndexOf($"/begin {inos}");
index = index == -1 ? content.Length : index;
indexes.Add(index);
}
index = indexes.Min();
return index;
其中 foreach
产生了预期的结果,但 Parallel.ForEach
没有。
为什么我的代码不是线程安全的?
这里只有一个index
变量,因为它被“捕获”了。这意味着多个线程可以为它争吵,而不是每个线程都有自己的版本。
考虑:
- 线程 A 计算
index = content.IndexOf($"/begin {inos}");
- 线程 B 计算
index = content.IndexOf($"/begin {inos}");
- 糟糕,线程 A 的版本刚刚被覆盖 - 线程 A 使用 B 刚刚更新的
index
计算index = index == -1 ? content.Length : index;
- 等等
要点是:由于线程争用而丢失了一个值。
只需移动 index
的声明即可解决此问题:
Parallel.ForEach(KeywordTypes.GetImplementedNamedObjects(), inos =>
{
var index = content.IndexOf($"/begin {inos}");
...
从根本上说,变量的作用域是由声明它的地方定义的。如果在局部方法/lambda 的 外部 声明变量,编译器会尊重您的要求,并且该变量在该局部方法/lambda 的所有使用之间共享;如果它在 内部 本地方法/lambda 中声明,则生命周期是该调用的本地,调用者之间不共享任何状态。
如果你想绝对确定你没有意外泄漏状态,lambda 上的 static
修饰符可以实现这一点,尽管它也可以防止 indexes
被访问,所以......可能这不是你需要的。