1

First, I am aware of questions like this:

reference assignment is atomic so why is Interlocked.Exchange(ref Object, Object) needed?

... but I am still uncertain if I can avoid using lock(){} in my case.

In my case I have a class which represents some state and only a SINGLE thread that ever modifies that state from time to time. There are many threads that read the state though.

Do I need Interlocked.Exchange() in my case on the state object? Do I absolutely have to use lock(){}

Here is my example code reduced to a bare minimum:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreadingExample
{
class State
{
    public int X { get; set; }
    public string Str { get; set; }
    public DateTime Current { get; set; }
}

class Example
{
    State state;
    CancellationTokenSource cts = new CancellationTokenSource();

    Task updater;
    List<Task> readers = new List<Task>();

    public void Run()
    {
        updater = Task.Factory.StartNew(() =>
        {
            while (!cts.Token.IsCancellationRequested)
            {
                // wait until we have a new state from some source
                Thread.Sleep(1000);

                var newState = new State() { Current = DateTime.Now, X = DateTime.Now.Millisecond, Str = DateTime.Now.ToString() };

                // critical part
                state = newState;
            }
        }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

        for (int i = 0; i < 10; i++)
        {
            readers.Add(Task.Factory.StartNew(() =>
            {
                while (!cts.Token.IsCancellationRequested)
                {
                    // critical part
                    var readState = state;

                    // use it
                    if (readState != null)
                    {
                        Console.WriteLine(readState.Current);
                        Console.WriteLine(readState.Str);
                        Console.WriteLine(readState.X);
                    }
                }
            }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default));
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        new Example().Run();
        Console.ReadKey();
    }
}
}
Community
  • 1
  • 1
Eiver
  • 2,594
  • 2
  • 22
  • 36
  • 1
    Is this your actual code? Who is responsible for setting `readState` to `null`? Are your readers really spinning the CPU constantly? Did you mean to use `Interlocked.Exchange` to read the `state` variable and replace it with `null` atomically, perhaps? – vgru Mar 29 '16 at 14:11
  • 1
    @Eiver Could you explain why you need Lockless synchronization? – gabba Mar 29 '16 at 15:02
  • Originally I was always using locks in a case like this. I have heard that it is possible to do lock-less synchronization in certain cases, but it tricky and a source of potential errors and documentation is vague. I wanted to learn how to do it right. – Eiver Mar 29 '16 at 15:21
  • 2
    My standard test for "are you ready to do low lock programming?" is whether you can figure out this puzzle: http://blog.coverity.com/2014/03/26/reordering-optimizations/ If you can't explain why it is possible for s and t to both be true then you don't understand the memory model well enough to do lock free programming. (Note: **I do not understand the memory model well enough to do anything but the simplest lock-free programming**. I rely on lock-free code written and reviewed by experts whenever possible.) – Eric Lippert Mar 29 '16 at 21:17
  • @Eiver Why make the code more complicated and harder to understand just to create more contention? I don't get it. This is clearly *not* a candidate for lockless synchronization. (Locks deschedule contending threads, minimizing contention. Lockless code increases contention by keeping contending code running.) – David Schwartz Mar 29 '16 at 21:50

4 Answers4

1

If you only have a single thread that is updating and only a single thread reading I don't believe that you will run into any runtime errors. Your example however shows 10 reader threads. That being said I don't think that you should just assume that you don't need anything to make your application thread safe. You should introduce locking at a minimum to ensure that your threads will play nicely with one another. Because your State object is a complex object when you read the values in your reader thread, you may not be getting everything as you expect. Without locking one or two properties might be changed but not the third during the read operation. Here is a sample of the modifications that I am talking about.

class State
{
    public int X { get; set; }
    public string Str { get; set; }
    public DateTime Current { get; set; }
}

class Example
{
    State state;
    CancellationTokenSource cts = new CancellationTokenSource();
    Object syncObj = new Object();
    Task updater;
    List<Task> readers = new List<Task>();

    public void Run()
    {
        updater = Task.Factory.StartNew(() =>
        {
            while (!cts.Token.IsCancellationRequested)
            {
                // wait until we have a new state from some source
                Thread.Sleep(1000);

                var newState = new State() { Current = DateTime.Now, X = DateTime.Now.Millisecond, Str = DateTime.Now.ToString() };

                // critical part
                lock(syncObj) {
                    state = newState;
                }
            }
        }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

        for (int i = 0; i < 10; i++)
        {
            readers.Add(Task.Factory.StartNew(() =>
            {
                while (!cts.Token.IsCancellationRequested)
                {
                    State readState = null;

                    // critical part
                    lock(syncObj) {
                        readState = state.Clone();
                    }

                    // use it
                    if (readState != null)
                    {
                        Console.WriteLine(readState.Current);
                        Console.WriteLine(readState.Str);
                        Console.WriteLine(readState.X);
                    }
                }
            }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default));
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        new Example().Run();
        Console.ReadKey();
    }
}

It's a small change but it would ensure that you are thread safe with regard to the State object.

dacke.geo
  • 233
  • 2
  • 13
  • Does locking just the assignment solve the problem. If reference assignment is atomic why lock it? I am not replacing properties one by one. I am replacing the whole object. If I had to use locks, should I lock the "use it" part too? – Eiver Mar 29 '16 at 14:23
  • 1
    @Eiver: The way your code is written, you don't need a lock. But note that you're never "clearing" this variable (i.e. setting it back to `null`). This is what needs to be done atomically by a single reader, and in that case you either need `Interlocked.Exchange`, or a simple `lock`. Otherwise, as soon as you set `state` to a certain value, *all* your readers will keep processing it, forever. – vgru Mar 29 '16 at 14:34
  • That is exactly what I want. I want all readers to have access to the same state until it is changed to a new one. This is not producer-consumer. The state in my case is more like a cache. – Eiver Mar 29 '16 at 14:50
  • 1
    The lock ensures that nothing can access your _State_ while inside the lock. Using the Interlocked.Exchange should give you more performance but with only 10 reading threads it would be a negligible performance difference and the lock seemed easier to write. @Groo is correct in their assumption that you will always be reading that same state forever. Since I can see that this exactly what you wanted then you should be good. Eiver did I answer your question? I'm just curious about the down vote. :-( – dacke.geo Mar 29 '16 at 14:59
  • I didn't down vote. I would be very happy to hear from the down-voter too, what is wrong with that answer exactly. We all want to arrive at the best solution and we all know that multi-threading may be tricky. – Eiver Mar 29 '16 at 19:12
  • 1
    Reference type assignment is atomic in .NET. It is not possible to have "one or two properties changed, but not a third one". Locking around assignment will only create [lock contention](http://stackoverflow.com/q/1970345/69809), meaning that 10 readers will compete for the lock millions of times per second, disallowing the writer to do the assignment which is atomic anyway. If, on the other hand, the updater is changing individual properties of the object after the assignment, then again locking around the assignment itself won't prevent the object to be mutated. – vgru Mar 29 '16 at 20:51
  • 1
    The only reason why a lock would be (functionally) justified is if you would set `state` to `null` inside the reader block, if you only wanted *only one* reader to able to process the state and set it back to `null` atomically. This would be slightly worse than `Interlocked` performance wise, but at least correct. – vgru Mar 29 '16 at 20:55
  • 1
    @Groo I'm sorry but can you tell me why the lock would not prevent the _State_ object from becoming mutated during the assignment when the lock would be around the entire assignment block. I am of course referring to the case where individual items are changed in the state. Granted the sample above was simplified in order to show a concept in a small amount of space. I am assuming that you gave me the down vote because of contention? – dacke.geo Mar 29 '16 at 21:17
  • 1
    You wrote that OP should introduce locking to ensure thread safety (the reasoning behind this seems to be that there is more than one reader thread). But this is not correct. Regarding the mutable-object locking scenario, you don't get any thread safety *unless* you lock all instructions that mutate it, *and* all instructions which read it (i.e. the entire `if` statement inside the reader, in this example). And if you want consistency (not to get "two out of three properties changed"), you need to keep the lock for the entire time you plan on using it, while all the other readers are waiting. – vgru Mar 29 '16 at 21:31
  • 1
    @Groo Doesn't the fact that he is creating a local copy of the _State_ called _readState_ solve this issue? Would he have to have done this readState = State.clone(); – dacke.geo Mar 29 '16 at 21:39
  • 1
    @dacke.geo: it's a local copy of the object reference, not the object itself. Both the `state` variable and 10 other `readState` variables are pointing to the same instance in memory. If you want to change two properties on that instance atomically, you need to ensure that no readers are allowed to access any of the properties non-atomically, meaning they should acquire a lock whenever they want to read these two properties in succession. That's why sharing a mutable object is a bad idea. So what I am basically saying is that it needs to be immutable, and then you don't need a lock at all. – vgru Mar 29 '16 at 21:45
  • 1
    @Groo Now I follow you. I might need some ice for the sting of that down vote however. ;-) – dacke.geo Mar 29 '16 at 21:47
  • 1
    Now I saw the last updated comment, and this is correct, cloning the object inside the lock would also work. But making the object immutable (by adding `readonly` to its fields) would move this burden to the compiler, because if you've made sure that its field cannot be mutated, then the only way for the writer to mutate it is to create a new instance with different property values, and then you need no locking. Feel free to update your answer with an example of mutating properties and `state.Clone()`, that would be useful. SO won't allow me to retract my vote unless the answer is edited. – vgru Mar 29 '16 at 21:49
  • @Groo Done. Also thank you for bearing with me. I learned something as well through this entire exercise. – dacke.geo Mar 29 '16 at 21:56
  • If I wanted to lock and avoid lock contention at the same time, I think it would be best to use ReaderWriterLock class instead of lock(){} in the above example case? – Eiver Mar 30 '16 at 07:17
1

When in Doubt use Locks

Unless you have actually noticed a performance issue or have a section that can be accessed by Async logic that may or may not be multithreaded and need to do waiting and signaling would recommend using locks for synchronization between threads.

That being said

Could use Thread.MemoryBarrier to do what you are asking

As long as you don't care about the accuracy of the data you are reading the issue you would end up hitting would have to do with compiler optimization that would reorder your instructions incorrectly because it is multithreaded.

A way you could avoid that is with the Thread.MemoryBarrier method.

            while (!cts.Token.IsCancellationRequested)
            {
                //No command before the barrier
                Thread.MemoryBarrier();
                //Can end up on this side of the barrier  

                // critical part
                var readState = state;

                // use it
                if (readState != null)
                {
                    Console.WriteLine(readState.Current);
                    Console.WriteLine(readState.Str);
                    Console.WriteLine(readState.X);
                }
            }

This is called a half fence, there is a lot more information here about fences explained much better than I ever could a free ebook, Threading in C# by Joseph Albahari

konkked
  • 3,161
  • 14
  • 19
  • What do you mean by "As long as you don't care about the accuracy of the data"? I can accept that not all threads get new data immediately, but they should get a consistent object, that is they either should get an old object (if there is some caching going on) or a new one. The new object should become visible to all threads in a reasonable time. – Eiver Mar 29 '16 at 14:27
  • 1
    @konkked: What "inaccuracies" are you talking about, in this case? Also, simply making the `state` variable `volatile` will yield the same behavior. – vgru Mar 29 '16 at 14:39
  • 2
    @Eiver: the problem with your suppositions -- that the view of memory is in some sense consistent, and that updates happen *eventually* -- unfortunately is not borne out by reality in situations where there are multiple variables involved. Without appropriate locks and barriers there is *no requirement* that either (1) all threads see a consistent ordering of memory operations, or (2) that updates made on one thread are eventually seen by another. When doing low-lock code you have to reason *any scenario allowed by the memory model can happen*, and that includes a lot of weird stuff. – Eric Lippert Mar 29 '16 at 21:25
1

The way your code is written right now, a single "updater" task sets the state to a certain value, and then all readers start reading it and processing it as soon as they have the chance to do it. And they keep reading this state forever, or until it changes.

You probably don't want 10 threads doing the same thing, and then doing this same thing again in the next cycle, until state gets changed.

Correct way to implement a single producer/multiple consumer

I would at least set the state to null atomically in one of the readers:

// read and swap with null atomically
var readState = Interlocked.Exchange(ref state, null);

This will still leave your readers spinning the CPU like crazy, and this is likely not something you wanted.

A better solution would be to use a BlockingCollection, which solves most of your problems:

BlockingCollection<State> queue = new BlockingCollection<State>();

updater = Task.Factory.StartNew(() =>
{
    while (!cts.Token.IsCancellationRequested)
    {
        Thread.Sleep(1000);

        var newState = GetNewState();

        queue.Add(newState);
    }
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

for (int i = 0; i < 10; i++)
{
    var readerId = i.ToString();

    readers.Add(Task.Factory.StartNew(() =>
    {
        while (!cts.Token.IsCancellationRequested)
        {
            // get it
            var readState = queue.Take(cts.Token);

            // use it
            if (readState != null)
            {
                Console.WriteLine("Hello from reader #" + readerId);
                Console.WriteLine(readState.X);
            }
        }
    }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default));
}

First of all, BlockingCollection<T>.Take will block all your readers until it's signalled from the writer (updater) thread. This means those threads are sitting doing nothing, and your CPU should be idle.

Also, it's neat that the method accepts a CancellationToken, meaning you don't have to worry about unblocking your readers when you're done.

Do you only need to share a read-only state?

If your intent is merely sharing some readonly state between threads (IMHO your example code doesn't express this intention clearly), then the correct way to prevent from shooting yourself in the foot would be:

  1. Make the state field volatile to prevent compiler and CPU magically reordering instructions and caching it, and

  2. Make all fields inside the State class readonly, to prevent yourself from modifying any of its fields once you assign it.

  3. Ensure that all fields inside the State class are either primitive types, or immutable structs/classes.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • My question is not about busy wating or a producer consumer problem. This is just an example code. I wanted to show an example, which could read state at any time, even most unexpected time. – Eiver Mar 29 '16 at 14:40
  • 1
    @Eiver: Then perhaps you should completely rephrase your question, because right now it says: *"only a SINGLE thread that ever modifies that state from time to time. There are many threads that read the state though"*. And then you have an example code which does exactly that. If you have many threads, you *need* some kind of a synchronization mechanism. In your example code, this "synchronization" would be (at least) swapping the state with null in one of the readers. You also stated that you understand that reads/writes are atomic in .NET. What exactly are you asking then? – vgru Mar 29 '16 at 14:43
  • 1
    @Eiver: I updated the answer, in case you only want to share read-only state between threads. – vgru Mar 29 '16 at 14:52
  • @ Groo sorry about not being clear enough. Sharing a common read-only state is exactly what I need. Its read-only with the exception that the writer can change it at any time. My question is: Is that enough to be thread-safe in this case? I thought about using Interlocked.Exchange() in the writer? – Eiver Mar 29 '16 at 15:15
  • 1
    @Eiver: `Interlocked.Exchange` is only needed if you want to read-and-swap in a single (atomic) operation. By "writer can change it at any time", you mean swap with a different reference, or change its properties? Because assignment *is* thread safe, but changing properties on a shared object is a completely different thing, and I would advise against it. That's why immutable (i.e. "read only") objects are the best way to ensure multithreaded correctness. – vgru Mar 29 '16 at 20:48
  • If you need to change a single property on your shared state, a better way would be to create an entirely new readonly clone (with the property set to a new value), than to mutate the original object. Make it immutable, and make your writer always assign a new instance to this variable in a single step. This also means that all the types of its fields need to be immutable, which is something *you* need to ensure (compiler won't help you there). Otherwise, once a reader gets a reference to a *mutable* object, it is very hard to ensure thread safety through language/compiler constructs. – vgru Mar 29 '16 at 21:02
1

This is unsafe if cts.Token.IsCancellationRequested does not cause a memory barrier. If it does not then the compiler is free to only read once and cache state locally.

I don't think it's documented whether cts.Token.IsCancellationRequested causes a memory barrier. Normally, these concerns are not documented.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    Looking at [the source code](http://referencesource.microsoft.com/#mscorlib/system/threading/CancellationToken.cs,86), I would say yes, because `m_state` in [`CancellationTokenSource`](http://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,116) is [volatile](http://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,65). – vgru Mar 29 '16 at 21:34
  • Well, I originally wanted to write while(true){} to make things even simpler. I used a cancellation token, because you typically use these in production code. You probably did notice that I never actually cancel these loops in the example code. – Eiver Mar 30 '16 at 07:07