我刚刚开始学习多线程,我遇到了这个问题:
public class Program1
{
int variable;
bool variableValueHasBeenSet = false;
public void Func1()
{
variable = 1;
variableValueHasBeenSet = true;
}
public void Func2()
{
if (variableValueHasBeenSet) Console.WriteLine(variable);
}
}
问题是:如果Func1()和Func2()在两个独立的线程上并行运行,则确定以下代码片段的所有可能输出(在控制台中)。答案是没有,1或0.前两个选项是显而易见的,但第三个让我感到惊讶,所以我想尝试得到它,这就是我尝试过的:
for (int i = 0; i < 100; i++)
{
var prog1 = new Program1();
List<Task> tasks = new List<Task>();
tasks.Add(new Task(() => prog1.Func2(), TaskCreationOptions.LongRunning));
tasks.Add(new Task(() => prog1.Func1(), TaskCreationOptions.LongRunning));
Parallel.ForEach(tasks, t => t.Start());
}
我无法得到0,只有1和1,所以我想知道我做错了什么以及如何测试这个具体问题?
这是他们为0提供的解释:
0 - 这似乎是不可能的,但这是一个可能的输出和一个有趣的输出。 .Net运行时,C#和CPU可以自由重新排序指令以进行优化。因此,variableValueHasBeenSet可能设置为true,但变量的值仍为零。这种输出的另一个原因是缓存。 Thread2可能会将变量的值缓存为0,并且当Thread1在Func1中更新它时,不会看到更新的值。对于单线程程序,这不是问题,因为排序是有保证的,但在多线程代码中则不然。如果两个位置的代码都被锁定,则可以减轻此问题。另一种先进的方法是使用记忆障碍。
答案 0 :(得分:3)
.Net运行时,C#和CPU可以自由重新排序 优化说明。
这一点信息非常重要,因为无法保证所有重新排序都会发生。
优化器通常会重新排序指令,但通常这是由代码复杂性触发的,通常只会在发布构建中发生(优化器会查找依赖链并可能决定重新排序代码,如果没有依赖项被破坏,它将导致更快/更紧凑的代码)。测试的代码复杂性非常低,可能无法触发重新排序优化。
同样的事情可能在CPU级别发生,如果在CPU指令之间没有找到依赖链,它们可能被重新排序或者至少由超标量CPU并行运行,但是其他更简单的体系结构将按顺序运行代码。
这种输出的另一个原因是缓存。 Thread2可能会缓存 变量的值为0,并且在何时不会看到更新的值 Thread1在Func1
中更新它
同样,这只是一种可能性。通常在重复访问循环中的变量时触发此类优化。优化器可以决定将变量放在CPU寄存器上而不是每次迭代从内存中访问它都会更快。
在任何情况下,对C#编译器如何发出代码的控制量都非常有限,IL代码如何转换为机器代码也是如此。由于这些原因,对于您想要证明的案例,您很难在每个架构上进行可重复的测试。
真正重要的是你需要注意1)指令的执行顺序永远不会被认为是理所当然的; 2)变量可以临时存储在寄存器中作为潜在的优化。一旦意识到你应该围绕这些可能性编写你的代码