-2

I have a simple test case that I borrowed from another question on here but modified with slightly different but simple contrived examples. Given:

class Foo
    {
        public bool Complete; // { get; set; }

        public bool IsComplete()
        {
           return Complete ;
        }

    }

    class Program
    {
        static Foo foo = new Foo();

        static void ThreadProc()
        {
            bool toggle = false;
          //  while (!foo.Complete) toggle = !toggle;
            while (!foo.IsComplete()) toggle = !toggle;

            Console.WriteLine("Thread done");
        }

        static void Main()
        {
            var t = new Thread(ThreadProc);
            t.Start();
            Thread.Sleep(1000);
            foo.Complete = true;
            t.Join();
        }

Given that ThreadProc is calling IsComplete() the compiler doesn't seem to cache the Complete variable. But however I can't find a guarantee that the compiler doesn't generate cache optimisations for method calls on an object passed from a different thread.

But I'm worried about this scenario: If ThreadProc is running on a different processor to main thread can it deep copy the entire code of object foo into its thread cache? Meaning I will be updating an entirely different object instance. If so would making the reference volatile necessary?

I don't understand what's happening here. But it seems to prove my worry above, it never exits (but exits in Debug mode):

class Foo
    {
        public bool Complete; // { get; set; }

        public bool IsComplete()
        {
           return Complete ;
        }

    }

    class Program
    {

        static void ThreadProc(Foo foo)
        {
            bool toggle = false;
          //  while (!foo.Complete) toggle = !toggle;
            while (!foo.IsComplete()) toggle = !toggle;

            Console.WriteLine("Thread done");
        }

        static void Main()
        {
            Foo foo = new Foo();

            var t = new Thread(()=>ThreadProc(foo));
            t.Start();
            Thread.Sleep(1000);
            foo.Complete = true;
            t.Join();
            Console.ReadLine();
        }
    }

Yet, the below completes! Pretty much the same thing written differently. I can't see how the anonymous lambda is changing things. It should still point to the same object instance:

public class Foo
    {
        public bool Complete; // { get; set; }

        private FooThread fooThread;
        public Foo()
        {
            fooThread = new FooThread(this);
        }
        public bool IsComplete()
        {
           return Complete ;
        }

        public void StartThread()
        {
            var t = new Thread(fooThread.ThreadProc);
            t.Start();
            Thread.Sleep(1000);
            Complete = true;
            t.Join();
        }
    }

    public class FooThread
    {
        private Foo foo;

        public FooThread(Foo f)
        {
            foo = f;
        }

        public void ThreadProc()
        {
            bool toggle = false;
            //  while (!foo.Complete) toggle = !toggle;
            while (!foo.IsComplete()) toggle = !toggle;

            Console.WriteLine("Thread done");
        }

    }
    class Program
    {

        static void Main()
        {
            Foo foo = new Foo();
            foo.StartThread();
            Console.ReadLine();
        }
    }

A delegate scenario...:

 public class Foo
{
    public bool Complete; // { get; set; }

    private FooThread fooThread;
    public Foo()
    {
        fooThread = new FooThread(this);
        fooThread.TriggerCompletion += SetComplete;
    }
    public bool IsComplete()
    {
        return Complete;
    }

    public void SetComplete()
    {
        Complete = true;
    }

    public Thread StartThread()
    {
        var t = new Thread(fooThread.ThreadProc);
        return t;
    }
}

public class FooThread
{
    private Foo foo;
    public event Action TriggerCompletion;

    public FooThread(Foo f)
    {
        foo = f;
    }

    public void ThreadProc()
    {
        bool toggle = false;
        //  while (!foo.Complete) toggle = !toggle;
        int i = 0;
        while (!foo.IsComplete())
        {
            toggle = !toggle;
            i++;
            if(i == 1200300) // contrived
                TriggerCompletion?.Invoke();
        }

        Console.WriteLine("Thread done");
    }

}
class Program
{

    static void Main()
    {
        Foo foo = new Foo();
        var t= foo.StartThread();
        t.Start();
        Thread.Sleep(1000);
        t.Join();
        Console.ReadLine();
    }
}

These are all contrived examples. But I'm not sure why 1 scenario isn't working. I only see 2 threads at work here updating a boolean value. So volatile shouldn't be necessary. Code should reasonably lock free as one or two dirty reads from Foo is OK. FooThread will signal completion infrequently. (I'm aware of TaskCancellationSource, this question isn't about cancellations but updating a boolean flag from a different thread via on object instance's method )

EDIT: Please test in release mode.

EDIT: Updates on the failing test case i.e. code block 2. It seems the compiler is making optimizations on !foo.IsComplete() method call. It appears to assume that the instance variable is not used else where so optimizes out the call - perhaps in its simplicity?

By having an instance variable referencing foo the compiler applies to make no such assumption such that the first code block now modified fails:

public class Foo
    {
        public bool Complete; // { get; set; }

        private FooThread fooThread;
        public Foo()
        {
            fooThread = new FooThread();
        }
        public bool IsComplete()
        {
            return Complete;
        }

        public void StartThread()
        {
            var t = new Thread(()=>fooThread.ThreadProc(this));
            t.Start();
            Thread.Sleep(1000);
            Complete = true;
            t.Join();
        }
    }

    public class FooThread
    {


        public void ThreadProc(Foo f)
        {
            bool toggle = false;
            //  while (!foo.Complete) toggle = !toggle;
            while (!f.IsComplete()) toggle = !toggle;

            Console.WriteLine("Thread done");
        }

    }
    class Program
    {

        static void Main()
        {
            Foo foo = new Foo();
            foo.StartThread();
            Console.ReadLine();
        }
    }

Also, by introducing an interface on Foo such that the call to foo.IsComplete is now virtual (IFoo.IsComplete) the compiler removes the optimization.

Is this guaranteed though?

Lews Therin
  • 10,907
  • 4
  • 48
  • 72
  • You should not really make any assumptions about what the optimising JIT compiler may or may not do. If a field may be updated by a thread other than the one reading it, you should protect it using [`Volatile.Read()`](https://msdn.microsoft.com/en-us/library/system.threading.volatile.read(v=vs.110).aspx) to read it and [`Volatile.Write()`](https://msdn.microsoft.com/en-us/library/system.threading.volatile.write(v=vs.110).aspx) to write it - or use `lock`. – Matthew Watson Aug 09 '17 at 15:15
  • [Use of the `volatile` keyword itself is to be avoided these days.](https://blogs.msdn.microsoft.com/ericlippert/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/) – Matthew Watson Aug 09 '17 at 15:15
  • You don't want to write a busyloop that just sits there melting an egg on your CPU while doing nothing productive in the first place. Use an appropriate synchronization mechanism that's specifically designed for solving whatever synchronization problem you happen to have. It will not only fix that problem, but you can trust that the object will be designed to be safely accessed from multiple threads; *it* will be responsible for ensuring whatever data is shared between threads is accessed correctly. – Servy Aug 09 '17 at 15:41
  • The loop is a contrived example. It could be a signal update from another thread or WCF service whatever. The thread is not necessarily spinning. I guess I could use volatile read/write. Is it guaranteed to write/read to main memory though? The usage seems to say it won't reorder but not about staleness. Also I will need to have a lock on Foo and FooThread which complicates things. Trying to avoid a lock if I Can – Lews Therin Aug 09 '17 at 15:45

1 Answers1

0

It seems to me that the bulk of your question is answered adequately here:
When should the volatile keyword be used in C#?,
and What is the purpose of 'volatile' keyword in C#,
and even Why this program does not go into infinite loop in absence of volatility of a boolean condition variable?

To address some more specific concerns you've expressed…

Should an object reference passed to a Thread be marked as volatile?

Marking the reference in your code example isn't the issue. It's the Complete field that is in question, and which needs to be volatile. The object reference never changes, so making it volatile doesn't matter.

There's a lot of different code in your question, much of the behavior dependent on exact version of compiler, runtime, OS, and CPU type. It's really too broad.

But, the basic question you seem to be asking can be answered simply enough: in the .NET environment, you are required to provide some means of synchronization between threads. All components involved are free to make whatever optimizations they want as long as they comply with the semantics of synchronization you've provided.

If you provide none, the code may or may not work as intended. The runtime is not required to not work when you don't provide synchronization (yes, all those negatives are intended), and so the fact that it does work in some cases even though you don't provide synchronization doesn't in any way absolve you from providing synchronization.

Are delegates affected?

It's not clear what you mean by that. The one example you have above which you describe as "a delegate scenario" doesn't involve any concurrent access to data. Unlike the other examples, the Complete field is only ever accessed by the extra thread you started, so there are no synchronization issues to address.

I only 2 threads at work here updating a boolean value. So volatile shouldn't be necessary

Grammatically confusing statement aside, the volatile keyword isn't a matter of degrees. It works only with primitive types or references (so that the value is a bool is no justification for omitting it), and all you need are two threads for there to be a need for synchronization. Why would you think that having "only 2 threads at work here updating a boolean value" would lead to the conclusion that volatile is unnecessary?

To be clear: that would not be a valid conclusion.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • "the volatile keyword isn't a matter of degrees" What does that mean? – Lews Therin Aug 10 '17 at 06:51
  • "I only 2 threads at work here updating a boolean value. So volatile shouldn't be necessary" - I meant locking not volatile – Lews Therin Aug 10 '17 at 06:52
  • _""the volatile keyword isn't a matter of degrees" What does that mean?"_ -- your statement seems to imply that if you had more threads, `volatile` would be needed, while it's not with only two threads. "Degrees" as in, you seem to believe (incorrectly) that the "degree" of concurrency (i.e. the number of concurrently running threads) makes a difference. – Peter Duniho Aug 10 '17 at 06:55
  • _"I meant locking not volatile"_ -- even so. Implementations with just two concurrent threads can still require locking. It just depends on what they are doing. You can have scenarios with _more_ than two threads where you can still synchronize with `volatile` only, and you can have scenarios with just two threads where using `lock` is more appropriate. Though, synchronizing using only `volatile` can be very tricky, and is definitely in the _"not recommended"_ category. Even with just two threads. – Peter Duniho Aug 10 '17 at 06:57
  • "*and you can have scenarios with just two threads where using lock is more appropriate*" - that's correct. That's why I tried to convey that for this use case due to the atomic nature of a bool I don't see the need for a lock. Assuming no code optimisations - my worry really was about cpu cache optimisations of an entire object in memory. – Lews Therin Aug 10 '17 at 07:31
  • "*The object reference never changes, so making it volatile doesn't matter.*" - if the object is cached by the CPU surely the reference is invalid? The cpu might decide to at some later time update the main memory reference. – Lews Therin Aug 10 '17 at 07:32
  • _"if the object is cached by the CPU surely the reference is invalid"_ -- the CPU doesn't cache objects. The CPU doesn't know anything about objects. It only knows about memory locations. Code accessing the `Complete` field will always re-retrieve the object reference as needed, but that reference never changes anyway, so even if the reference were cached (say, for example, the compiler enregisters it), `volatile` would still not be needed. – Peter Duniho Aug 10 '17 at 08:03
  • Peter, sorry for being dumb but are you sure about that? If the cpu sees that the object memory location is accessed frequently isn't it conceivable that it could map that object's memory location from main to its register? Therefore, the cpu maintains a symbolic reference to the main memory. So in short: Thread 1 accessing the object on CPU is hot, Thread 2 accessing same object on CPU 2 but not as hot therefore still goes to main mem. If that scenario isn't possible then I understand now that volatile is necessary at a *compiler level* – Lews Therin Aug 10 '17 at 10:45
  • _"If the cpu sees that the object memory location is accessed frequently isn't it conceivable that it could map that object's memory location from main to its register?"_ -- the CPU is never going to map memory locations to registers. That's the job of the compiler. – Peter Duniho Aug 10 '17 at 15:42