3

I am probably making a mistake in code below, but where?

MCVE:

class Program
{
    struct Test
    {
        public readonly string A;
        public readonly string B;
        public Test(string test) => A = B = test;
    }

    static void Main(string[] args)
    {
        var test = new Test();
        int counter = 0;
        Task.Run(() =>
        {
            while (true)
            {
                counter++;
                test = new Test(counter.ToString());
            }
        });
        Task.Run(() =>
        {
            Thread.Sleep(100);
            while (true)
            {
                var copy = test; // create a copy of test
                if (copy.A != copy.B)
                    Console.WriteLine($"{copy.A} {copy.B}"); // occurs often, why?
            }
        });
        Console.ReadKey();
    }
}

Output on my PC:

808317 808318
812792 812793
814938 814939
815423 815428
815779 815780
816280 816281
816915 816916

Why?

It looks like creating copy is a problem. Right? Of course I can use lock to access test, but that means that only immutable struct with single member can be thread-safe.

mjwills
  • 23,389
  • 6
  • 40
  • 63
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • @mjwills, my expectation is nothing should be printed. I was hoping that immutable struct is thread safe, but somehow it's not. I don't want to use `lock` to access test. – Sinatr Sep 16 '19 at 09:05
  • 2
    The problem is that `var copy = test; // create a copy of test` copies the `test` value **while** another thread writes to it. Unfortunately, the write is not atomic - so it copies part of the old value, part of the new value. You can verify this by changing the constructor to a normal format (`{` and `}`) and putting a conditional breakpoint on the line after `A = B = test;` This **proves** the issue is not in the constructor. – mjwills Sep 16 '19 at 09:05
  • The duplicate also discusses why it works with `int` but not `string`. – mjwills Sep 16 '19 at 09:09
  • Note that you have the [same problem with `double`](https://stackoverflow.com/q/3676808/87698): The *type* itself is immutable, but simultaneously writing and reading the same *mutable* variable will lead to partial reads. – Heinzi Sep 16 '19 at 09:11
  • `I was hoping that immutable struct is thread safe` To be clear, the `struct` **is** thread-safe. Just like with `DateTime` (https://stackoverflow.com/a/14345231/34092). That doesn't mean you can **overwrite** a value without impact though (since thread-safety is in relation to a single value of the struct, not in relation to a second value **overwriting** the first). – mjwills Sep 16 '19 at 09:35
  • The concept "thread safe" is not well defined. You have to describe what behavior you expect from a "thread safe" piece of code or data structure, and a thread safe data structure has to describe what kind of thread safe it provides. The structs themselves are thread safe, as in "no threads can modify them", copying them isn't thread-safe, as you've noticed. – Lasse V. Karlsen Sep 16 '19 at 09:57
  • @LasseVågsætherKarlsen, copying is thread safe as long as size of struct is exactly 1, 2, 4 or 8 bytes ([source](https://stackoverflow.com/a/4940183/1997232) and I quickly tested it, so it seems to be true). I thought copying of struct is some magic, what is guaranteed to be thread safe, now I know it's not. Mostly I am working with reference types, using local variable to *copy* current instance is a common approach. I though something similar happens with value types, my bad. – Sinatr Sep 16 '19 at 11:30

0 Answers0