4

Seeing is believing. Can anyone reproduce a program that reads a torn decimal? I tried spinning up multiple threads changing the same decimal between 1 and 2. I did not catch any reads different from 1 or 2.

I like to see that a reader thread does not see a atomic change from a writer thread, so the value should be something different from 1 or 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());
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Stig
  • 1,974
  • 2
  • 23
  • 50
  • 4
    What is a "torn decimal"? – Willem van Rumpt Apr 24 '14 at 07:32
  • do you have some source code to share with us? – Joel Apr 24 '14 at 07:34
  • After reading this question, it is clear to me that I have not yet had enough coffee this morning. – Lee White Apr 24 '14 at 07:34
  • 2
    I know what he means. He's talking about "torn reads" which occur when a value type which does not have atomic read/write operations is updated in one thread while another thread is reading it, resulting in a corrupted value. – Matthew Watson Apr 24 '14 at 07:39
  • Look here: http://stackoverflow.com/a/11360861/98491 – Jürgen Steinblock Apr 24 '14 at 07:39
  • Obvious not a very clear question. I have added some code – Stig Apr 24 '14 at 07:47
  • SchlaWiener > Doesn't reproduce a torn read. – Stig Apr 24 '14 at 07:49
  • 1
    I suggest you choose the two decimal numbers 0M and decimal.MaxValue. Get threads to constantly read and write those values and see if you get a torn value back on the read. The problem with 1M and 2M is that their bits are the same except for the lowest byte and so they will never get a torn value back as most bytes are "0". – Chris Walsh Apr 24 '14 at 08:09
  • 2
    Also check this msdn article, it has a sample on read/write tearing: http://msdn.microsoft.com/en-us/magazine/cc817398.aspx – jeroenh Apr 24 '14 at 08:20

1 Answers1

6

This code will demonstrate a torn read of a 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;
    }
}

It happens faster in a release build than a debug build.

I think the reason that you weren't seeing a torn read in your test code is because you were only changing the value between 0 and 1. It's likely that the bits being changed during your test are all in the same word being used to store the value internally, and accesses to words are atomic.

By changing the value between 1 and 10000000000, we force bits to change in two different words, allowing a torn read to be observed.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • I just tried the same experiment with `Int64` and the values `0x0000000000000000` and `0xFFFFFFFFFFFFFFFF` and was also unable to repo the tear. I could with `0x1234567890ABCDEF` and `0xFEDCBA0987654321`. Would this also be due to words being atomic? – BanksySan May 21 '18 at 20:18
  • @BanksySan Yes, when running 64-bit code then reads and writes of 64-bit integral values are atomic. – Matthew Watson May 22 '18 at 10:04
  • @MatthewWatson That's it. It should work if Inforced 32 bit mode then. – BanksySan May 22 '18 at 10:05
  • @MatthewWatson Just tested it. If I add the `/platform:x86` switch to the compiler then the test works! – BanksySan May 22 '18 at 10:55
  • cannot reproduce the behaviour in .net 6 and with x86 enabled – julian bechtold Jul 01 '22 at 08:17
  • @julianbechtold You sure you're using a RELEASE build? I just tried it - it happens in a release build, but now it doesn't happen in debug build (possibly because of compiler changes in the last 8 years) – Matthew Watson Jul 01 '22 at 08:49
  • ahh. Thank you. switching to release switched the cpu configuration back. So. As I understand it, this issue is irrelevant on x64 builds. Is that correct? – julian bechtold Jul 01 '22 at 09:36
  • 1
    @julianbechtold Yes, 64-bit code doesn't suffer from this problem so much. However, it *can* still occur - it's just harder to reproduce. A `Decimal` is 128 bits, so it's susceptible to torn reads even on 64-bit architecture. – Matthew Watson Jul 01 '22 at 09:53