【问题标题】:Reproduce torn reads of decimal in c#在 c# 中重现十进制的撕裂读数
【发布时间】:2014-04-24 07:27:36
【问题描述】:

眼见为实。任何人都可以重现读取小数点的程序吗?我尝试启动多个线程,在 1 和 2 之间更改相同的小数。我没有捕获任何不同于 1 或 2 的读取。

编辑:我喜欢看到读取器线程看不到写入器线程的原子更改,因此该值应该不同于 1 或 2。

void TornDecimalReadTest()
    {
        decimal sharedDecimal = 1;
        int threadCount = 100;
        var threads = new List<Thread>();

        for (int i = 0; i < threadCount; i++)
        {
            int threadId = i;
            var thread = new Thread(() =>
            {
                Thread.Sleep(5000);

                decimal newValue = threadId % 2 == 0 ? 1 : 2;
                bool isWriterThread = threadId % 2 == 0;

                Console.WriteLine("Writer : " + isWriterThread + " - will set value " + newValue);

                for (int j = 0; j < 1000000; j++)
                {
                    if (isWriterThread)
                        sharedDecimal = newValue;

                    decimal decimalRead = sharedDecimal;

                    if (decimalRead != 1 && decimalRead != 2)
                        Console.WriteLine(decimalRead);
                }
            });

            threads.Add(thread);
        }

        threads.ForEach(x => x.Start());
        threads.ForEach(x => x.Join());
    }

【问题讨论】:

  • 什么是“残缺的小数”?
  • 您有一些源代码与我们分享吗?
  • 我知道他的意思。他说的是“撕裂读取”,这种情况发生在一个线程中更新一个没有原子读/写操作的值类型,而另一个线程正在读取它,导致值损坏。
  • 我建议你选择0M和decimal.MaxValue这两个十进制数。让线程不断地读取和写入这些值,看看你是否在读取时得到一个撕裂的值。 1M 和 2M 的问题是它们的位是相同的,除了最低字节,所以它们永远不会得到一个撕裂的值,因为大多数字节都是“0”。
  • 还可以查看这篇 msdn 文章,它有一个关于读/写撕裂的示例:msdn.microsoft.com/en-us/magazine/cc817398.aspx

标签: c# atomic lock-free


【解决方案1】:

此代码将演示对 Decimal 的撕裂读取:

using System;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        void run()
        {
            Task.Run((Action) setter);
            Task.Run((Action) checker);

            Console.WriteLine("Press <ENTER> to stop");
            Console.ReadLine();
        }

        void setter()
        {
            while (true)
            {
                d = VALUE1;
                d = VALUE2;
            }
        }

        void checker()
        {
            for (int count = 0;; ++count)
            {
                var t = d;

                if (t != VALUE1 && t != VALUE2)
                    Console.WriteLine("Value is torn after {0} iterations: {1}", count, t);
            }
        }

        static void Main()
        {
            new Program().run();
        }

        Decimal d;

        const Decimal VALUE1 = 1m;
        const Decimal VALUE2 = 10000000000m;
    }
}

发布版本比调试版本更快。

我认为您在测试代码中没有看到撕裂读取的原因是因为您只是在 0 和 1 之间更改值。很可能在测试期间更改的位都在同一个单词中用于在内部存储值,并且对单词的访问是原子的。

通过在 1 和 10000000000 之间更改值,我们强制位在两个不同的单词中发生变化,从而可以观察到撕裂的读取。

【讨论】:

  • 我刚刚用Int640x00000000000000000xFFFFFFFFFFFFFFFF 的值尝试了相同的实验,但也无法恢复撕裂。我可以使用0x1234567890ABCDEF0xFEDCBA0987654321。这是否也是由于单词是原子的?
  • @BanksySan 是的,当运行 64 位代码时,64 位整数值的读写是原子的。
  • @MatthewWatson 就是这样。如果是强制 32 位模式,它应该可以工作。
  • @MatthewWatson 刚刚测试过。如果我将/platform:x86 开关添加到编译器,那么测试就可以了!
猜你喜欢
  • 1970-01-01
  • 2012-02-18
  • 2012-12-09
  • 1970-01-01
  • 2021-02-25
  • 2011-04-04
  • 2012-06-15
  • 2011-04-09
  • 1970-01-01
相关资源
最近更新 更多