3

I understand that a thread can cache a value and ignore changes made on another thread, but I'm wondering about a variation of this. Is it possible for a thread to change a cached value, which then never leaves its cache and so is never visible to other threads?

For example, could this code print "Flag is true" because thread a never sees the change that thread b makes to flag? (I can't make it do so, but I can't prove that it, or some variation of it, wouldn't.)

var flag = true;
var a = new Thread(() => {
    Thread.Sleep(200);
    Console.WriteLine($"Flag is {flag}");
});
var b = new Thread(() => {
    flag = false;
    while (true) {
        // Do something to avoid a memory barrier
    }
});

a.Start();
b.Start();

a.Join();

I can imagine that on thread b flag could be cached in a CPU register where it is then set to false, and when b enters the while loop it never gets the chance to (or never cares to) write the value of flag back to memory, hence a always sees flag as true.

From the memory barrier generators listed in this answer this seems, to me, to be possible in theory. Am I correct? I haven't been able to demonstrate it in practice. Can anyone come up with a example that does?

Community
  • 1
  • 1
eoinmullan
  • 1,157
  • 1
  • 9
  • 32
  • http://stackoverflow.com/questions/6581848/memory-barrier-generators – Matthew Watson Jun 17 '16 at 10:49
  • @MatthewWatson, thanks, that link answers the question I originally asked, but not what I'm trying to find out. I've edited the question to reflect this. I'm actually chasing a bug that could be explained by what I'm describing above, but I want to know if I'm correct in my reasoning, and how likely this is in reality. – eoinmullan Jun 17 '16 at 13:26
  • OK, I've reopened this question now it's been changed. – Matthew Watson Jun 18 '16 at 09:17
  • The question could be improved by stating the programming language involved. From the tags, I'm guessing it's C#. – Arch D. Robison Jun 18 '16 at 17:10
  • "I'm actually chasing a bug ..." - we may be able to help you better if you can create an [mcve] that recreates this bug, rather than trying to help you prove negatives. – Damien_The_Unbeliever Jun 22 '16 at 08:25
  • I appreciate that, but the bug is in a large code base and it's intermittent. If I could provide a MCVE I'd be in a much better position to understand it, but unfortunately I can't. My question here describes one possible theory which I'm unable to demonstrate with a MCVE. I guess I'm asking can someone else prove for falsify my theory. (I now believe it to be incorrect based on your excellent answer below, which I'm currently considering) – eoinmullan Jun 22 '16 at 08:55

4 Answers4

2

Is it possible for a thread to change a cached value, which then never leaves its cache and so is never visible to other threads?

If we're talking literally about the hardware caches, then we need to talk about specific processor families. And if you're working (as seems likely) on x86 (and x64), you need to be aware that those processors actually have a far stronger memory model than is required for .NET. In x86 systems, the caches maintain coherency, and so no write can be ignored by other processors.

If we're talking about the optimization wherein a particular memory location has been read into a processor register and then a subsequent read from memory just reuses the register, then there isn't a similar analogue on the write side. You'll note that there's always at least one read from the actual memory location before we assume that nothing else is changing that memory location and so we can reuse the register.

On the write side, we've been told to push something to a particular memory location. We have to at least push to that location once, and it would likely be a deoptimization to always store the previously known value at that location (especially if our thread never reads from it) in a separate register just to be able to perform a comparison and elide the write operation.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • This almost answers the question completely, but can I clarify something? "We have to at least push to that location once" - Is this push part of the write operation? i.e. it's not something that could be delayed? – eoinmullan Jun 22 '16 at 09:20
  • 1
    @eoinmullan - yes, I was just trying to avoid overusing write. It's not something that's delayed. – Damien_The_Unbeliever Jun 22 '16 at 10:00
1

In order from easiest to do correctly to easiest to screw up

  1. Use locks when reading/writing
  2. Use the functions on the Interlocked class
  3. Use memory barriers
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
0

I'm not entirely sure that this answers your question, but here goes.

If you run the release (not debug) version of the following code, it will never terminate because waitForFlag() never sees the changed version of flag.

However, if you comment-out either of the indicated lines, the program will terminate.

It looks like making any call to an external library in the while (flag) loop causes the optimiser to not cache the value of flag.

Also (of course) making flag volatile will prevent such an optimisation.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        void run()
        {
            Task.Factory.StartNew(resetFlagAfter1s);
            var waiter = Task.Factory.StartNew(waitForFlag);

            waiter.Wait();
            Console.WriteLine("Done");
        }

        void resetFlagAfter1s()
        {
            Thread.Sleep(1000);
            flag = false;
        }

        void waitForFlag()
        {
            int x = 0;

            while (flag)
            {
                ++x;
                // Uncommenting this line make this thread see the changed value of flag.
                // Console.WriteLine("Spinning"); 
            }
        }

        // Uncommenting "volatile" makes waitForFlag() see the changed value of flag.
        /*volatile*/ bool flag = true;

        static void Main()
        {
            new Program().run();
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    This illustrates what happens in the question I originally linked to, i.e. a thread caches a value and subsequently misses an update to that value made on another thread. I'm wondering if it's possible to see a variation of this where a thread caches a value and modifies its cached value, but that change is never seen on other threads, regardless of whether not they re-read the value. – eoinmullan Jun 20 '16 at 08:44
0

Research Thread.MemoryBarrier and you will be golden.

keith
  • 5,122
  • 3
  • 21
  • 50