6

Note I tend to write lock-free code so I try to avoid any types of lock where possible. Instead I just use while(true) loop because I have a lot of CPU power.

According to that http://msdn.microsoft.com/en-us/library/aa691278%28VS.71%29.aspx double variable update is not atomic.

I'm concerned about two issues:

  • if one thread modify variable of field or property and another thread read it at the same time i want to have either previous or new value, but I don't want to receive something strange. I.e. if one thread changes value from 5.5 to 15.15 I want to have in another thread one of these two numbers, but not 5.15 or 15.5 or anything else.
  • if one thread already updated value and another thread read it after that I want to receive latest, up to date, value. I was thinking that volatile keyword can help with that but in seems it can't, because "Volatile does not guarantee freshness of a value. It prevents some optimizations, but does not guarantee thread synchronization." as said here are c# primitive arrays volatile?

Questions:

  • Am I correct that without synchronization both of these issues may appear?
  • If you can give me short example which proves that without synchronization it will not work - that would be nice
  • How should I access double field or variable or property to have always real up to date value? Will "synchronization" guarantee "freshness"? What would be the fastest way for that? spinlock or something?

Currently I use a lot of double and decimal variables/field/properties in my program and almost everythig works fine, so I really confused because I ofthen access them from different threads without any synchronization and that just works... But now I'm thinking that probably it would be better to use float to have "built-in syncrhonization"

Community
  • 1
  • 1
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • When you use `double`, is the field shared (static etc)? Or is the field always in an instance of a class? – Davin Tryon Jul 06 '12 at 10:49
  • @dtryon it's almost always in instance of a class, I rarely use static fields. – Oleg Vazhnev Jul 06 '12 at 10:58
  • If you are not sharing the double, I don't *think* you will have any thread contention issues. – Davin Tryon Jul 06 '12 at 11:09
  • `lock` the doubles and save yourself from some excruciating future debugging pain. Also, `while(true)`s should be killed with fire on sight before they come back to bite you (and they *will*). – Alex Jul 06 '12 at 11:46
  • @Alex from here http://stackoverflow.com/questions/11306205/in-hft-does-it-make-sense-to-try-to-parallel-orders-processing `just synced queues with one tas processing messages on each of them. Can be super fficient with a no-lock approach` `I mostly run infinite loops - alternatively I use a thread pool, dependson usage. BlockingCOllection is highly inefficient, I have my own corcular buffer classes that use a non locking approach.` – Oleg Vazhnev Jul 06 '12 at 12:54

4 Answers4

3

Yes, you need to do something. double and decimal are not guaranteed to be atomic, so if you don't protect it you could get a torn value - i.e. your first bullet is entirely correct.

Re volatile; it is moot; you are not allowed to have a volatile field that is double or decimal, so the simplest answer is: lock.

Getting double to fail is a royal PITA; but here's a torn-value example featuring decimal (note the numbers of success/fail will change each iteration, even though the data is the same; this is the randomness of thread scheduling):

using System;
using System.Threading;
static class Program
{
    private static decimal shared ;
    static void Main()
    {
        Random random = new Random(12345);
        decimal[] values = new decimal[20];
        Console.WriteLine("Values:");
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = (decimal)random.NextDouble();
            Console.WriteLine(values[i]);
        }
        Console.WriteLine();
        object allAtOnce = new object();
        int waiting = 10;
        shared = values[0];
        int correct = 0, fail = 0;
        for(int i = 0 ; i < 10 ; i++)
        {
            Thread thread = new Thread(() =>
            {
                lock(allAtOnce)
                {
                    if (Interlocked.Decrement(ref waiting) == 0)
                    {
                        Monitor.PulseAll(allAtOnce);
                    } else
                    {
                        Monitor.Wait(allAtOnce);
                    }
                }
                for(int j = 0 ; j < 1000 ; j++)
                {
                    for(int k = 0 ; k < values.Length ; k++)
                    {
                        Thread.MemoryBarrier();
                        var tmp = shared;
                        if(Array.IndexOf(values, tmp) < 0)
                        {
                            Console.WriteLine("Invalid value detected: " + tmp);
                            Interlocked.Increment(ref fail);
                        } else
                        {
                            Interlocked.Increment(ref correct);
                        }
                        shared = values[k];
                    }
                }
                if (Interlocked.Increment(ref waiting) == 10)
                {
                    Console.WriteLine("{0} correct, {1} fails",
                        Interlocked.CompareExchange(ref correct, 0, 0),
                        Interlocked.CompareExchange(ref fail, 0, 0));
                    Console.WriteLine("All done; press any key");
                    Console.ReadKey();
                }
            });
            thread.IsBackground = false;
            thread.Start();
        }
    }
}

The key point; the language makes no guarantees for the atomicity of double. In reality, I expect you'll be fine, but most subtle problems caused by threading are due to using "I expect" instead of "I can guarantee".

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • so `volatile` is only for primitive types, and not primitive types will be always up to date? but how to make primitive types always up-to-date as `volatile` doesn't guarantee that? – Oleg Vazhnev Jul 06 '12 at 11:04
  • Can I use `Interlocked` to updated `double` value and how is that implemented? Is it supported by processor somehow? – Oleg Vazhnev Jul 06 '12 at 11:05
  • Marc, am I not understanding something? If OP is always using double inside and new instance, would there still be thread contention? – Davin Tryon Jul 06 '12 at 11:08
  • @javapowered there's `Interlocked` support for `double`, yes; that works fine. It isn't so much about *primitive*; after all, `double` is primitive; it is for a pre-defined set of types, generally small ones, plus references. – Marc Gravell Jul 06 '12 at 11:08
  • @dtryon firstly, the OP also mentions `decimal`. Secondly, giving it a new value is missing the point; that update **is not guaranteed to be atomic**. For `double`, I **expect** you'd struggle to reproduce an issue on an x64 CPU, but on x86: absolutely. – Marc Gravell Jul 06 '12 at 11:10
  • @MarcGravell I don't know what to choose. I can convert double to float and use float where multithreading access is supposed. Or I can use Interlocked. For me it seems that using float is easier, but i'm afraid that for float I will not receive "fresh" value and instead I will receive some "cached" value, how to avoid that? – Oleg Vazhnev Jul 06 '12 at 11:11
  • @javapowered for the array-of-double scenario, `Interlocked` is ideal – Marc Gravell Jul 06 '12 at 11:12
  • @javapowered (re last) simple: *use `Interlocked`* – Marc Gravell Jul 06 '12 at 11:12
  • @javapowered I added a clear and repeatable example of torn values with `decimal`, btw – Marc Gravell Jul 06 '12 at 11:22
  • thank you Mark! i will analyze your program later today and will write here if I will have any questions – Oleg Vazhnev Jul 06 '12 at 13:49
  • @MarcGravell i modified your example to use `double` and everything works find on bot x86 and x64 machine. does it mean that I can assume that double assignment is atomic, and so I should worry only about decimals? – Oleg Vazhnev Jul 06 '12 at 19:57
  • @javapowered no, because "assume" is never good with threading. There are some behaviours that are guaranteed. The one you are assuming is ***not*** one of them. When threading, only rely on *guaranteed* behaviour. – Marc Gravell Jul 06 '12 at 20:17
  • @MarcGravell it's still interesting if using this particular my computer and this particular version .NET framework is it guaranteed for double to be atomic. because tests show that it is guarenteed, but probably i will fail once per 10^10 of tries – Oleg Vazhnev Jul 07 '12 at 05:50
  • @javapowered "I can't reproduce something different" is different to "it always does this", which in turn is different to "it is guaranteed". My advice: if threading: stick to what is guaranteed. – Marc Gravell Jul 07 '12 at 06:15
0

If you want to guarantee that a block of code will be executed and finished before another thread manipulates it, surround that block of code with a lock.

You may be lucky, and threads may never battle over using a variable, but to make sure that it never happens, making sure precautions are taken wouldn't hurt.

Take a look here - this might help: http://msdn.microsoft.com/en-us/library/ms173179%28v=vs.80%29.aspx

Nathan White
  • 1,082
  • 7
  • 21
0

a general answer would be - updates should be synchronized for all "shared" variables. For exact answer need to see the code snippet.

amit
  • 1
0

Yes, you need to lock to be sure you get the correct result if multiple threads read/write a double at the same time.

Here's a failing example

[TestFixture]
public class DoubleTest
{
    private double theDouble;

    [Test]
    public void ShouldFailCalledParallell()
    {
        theDouble = 0;
        const int noOfTasks = 100;
        const int noOfLoopInTask = 100;
        var tasks = new Task[noOfTasks];
        for (var i = 0; i < noOfTasks; i++)
        {
            tasks[i] = new Task(() =>
                                    {
                                        for (var j = 0; j < noOfLoopInTask; j++)
                                        {
                                            theDouble++;
                                        }
                                    });
        }
        foreach (var task in tasks)
        {
            task.Start();
        }
        Task.WaitAll(tasks);
        theDouble.Should().Be.EqualTo(noOfTasks * noOfLoopInTask);
    }
}
Roger
  • 1,944
  • 1
  • 11
  • 17
  • The test shown is just a missing update one, though; that relates to *multiple related* operations - is usually a more obvious thing to guard against that the more subtle issues. – Marc Gravell Jul 06 '12 at 11:24
  • That's a different issue - not related to torn reads which is the question here. Your example would eventually fail because ++ is not an atomic operation(it's a read + write) – Ventsyslav Raikov Jul 06 '12 at 11:33