2

我有以下代码:

    public static System.Int32 i = 0;

    static void Main(string[] args)
    {
        new Thread(Worker1) { IsBackground = true }.Start();
        new Thread(Worker2) { IsBackground = true }.Start();

        Console.WriteLine("Running... Press enter to quit");
        Console.ReadLine();
    }

    static void Worker1(object _)
    {
        while (true)
        {
            var oldValue = i;
            i++;
            var newValue = i;

            if (newValue < oldValue)
                Console.WriteLine("{2}, i++ went backwards! oldValue={0}, newValue={1}!", oldValue, newValue, DateTime.Now.ToString("HH:MM:ss.fff"));
        }
    }

    static void Worker2(object _)
    {
        while (true)
        {
            i++;
        }
    }

运行时,这会产生如下所示的输出:

17:08:17.020, i++ went backwards! oldValue=6124653, newValue=6113984!
17:08:17.057, i++ went backwards! oldValue=18764535, newValue=18752368!
17:08:17.086, i++ went backwards! oldValue=27236177, newValue=27236176!
17:08:17.087, i++ went backwards! oldValue=27550457, newValue=27535008!
17:08:17.130, i++ went backwards! oldValue=40251349, newValue=40235492!
17:08:17.137, i++ went backwards! oldValue=42339974, newValue=42323786!
17:08:17.142, i++ went backwards! oldValue=43828602, newValue=43828436!
17:08:17.149, i++ went backwards! oldValue=45969702, newValue=45959111!
17:08:17.158, i++ went backwards! oldValue=48705847, newValue=48705549!
17:08:17.230, i++ went backwards! oldValue=71199684, newValue=71199674!

注意:这是在 Windows 7 上具有超线程的四核 i7 上运行的

据我所知,要么:

  • Thread2 在读取 oldValue 和 newValue 之间增加了大约 40 亿次(但不完全!)。不过,考虑到上面的数字和时间,我在接下来的 2 秒内中奖 10 次的可能性似乎更高。

  • CPU 和编译器正在做一些重新排序...这似乎是合乎逻辑的解释,但我无法弄清楚实际上可能导致这种情况的一系列操作?

任何人都可以对此有所了解吗?


为了澄清这个问题:我特意寻找可以重现内存重新排序错误的代码,作为教育练习的一部分。有很多方法可以解决这个问题,我主要感兴趣的是分析正在发生的事情。

@Tymek 已经在下面展示了(现在已经指出的很明显)答案。

4

3 回答 3

4

你肯定知道 i++ 不是原子的,看起来真的像这样:

  • 从内存读取i到寄存器,
  • 增加寄存器,
  • 将寄存器写入内存在哪里i
  • 返回i

这个序列可以在任何阶段中断。

考虑到这一点,产生厄运信息的一种情况如下:

  • 让我们i成为100
  • 线程2读取100并被中断;
  • 线程 1 执行一段时间并将 i 递增到 120(几个完整循环),然后读取i120,读取i并将其递增到121并在之前中断var newValue = i;
  • 线程 2 递增i101并被中断;
  • 线程一运行并执行var newValue = i<- 这将读入101

现在oldValue120newValue101

于 2012-08-02T08:11:53.417 回答
1

如果您希望旧值的分配、i 的递增和新值的分配“同时”发生,您应该使用锁。只需引入一个私有变量,然后对此使用锁。这看起来像:

lock(lockObj)
{
  var oldValue = i;             
  i++;             
  var newValue = i;
}
于 2012-08-02T06:13:36.020 回答
1

为避免此问题,您可以使用Interlocked.Increment它将保证操作将是原子的和线程安全的。当您使用 i++ 时,它不是原子的,并且它的值可以在增量期间更改(例如从另一个线程)。

于 2012-08-02T06:28:49.923 回答