Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safe DateTime update using Interlocked.*

Can I use an Interlocked.* synchronization method to update a DateTime variable?

I wish to maintain a last-touch time stamp in memory. Multiple http threads will update the last touch DateTime variable.

I appreciate that DateTime variables are value types that are replaced rather than updated.

The best I can come up with is to hold the timestamp as total ticks in a long

class x
{
  long _lastHit;

  void Touch()
  {
    Interlocked.Exchange( ref _lastHit, DateTime.Now.Ticks );   
  }
}
like image 858
camelCase Avatar asked Oct 07 '09 13:10

camelCase


People also ask

Why to use Interlocked in c#?

The methods of this class help protect against errors that can occur when the scheduler switches contexts while a thread is updating a variable that can be accessed by other threads, or when two threads are executing concurrently on separate processors.

Is DateTime thread safe C#?

DateTime is an immutable value type (struct). You cannot change an instance once created. It will not get corrupted and is thread safe.

What are Interlocked functions?

The interlocked functions provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. They also perform operations on variables in an atomic manner. The threads of different processes can use these functions if the variable is in shared memory.

What is Interlocked in c#?

It lets you do small and well-defined operations safely in a multi-threaded environment: for instance, if you want two threads to increment the same variable, you can use Interlocked to do it instead of acquiring a heavyweight lock and using the "regular increment".


1 Answers

Option 1: use a long with Interlocked and DateTime.ToBinary(). This doesn't need volatile (in fact you'd get a warning if you had it) because Interlocked already ensures an atomic update. You get the exact value of the DateTime this way.

long _lastHit;

void Touch()
{
    Interlocked.Exchange(ref _lastHit, DateTime.Now.ToBinary());
}

To read this atomically:

DateTime GetLastHit()
{
    long lastHit = Interlocked.CompareExchange(ref _lastHit, 0, 0);
    return DateTime.FromBinary(lastHit);
}

This returns the value of _lastHit, and if it was 0 swaps it with 0 (i.e. does nothing other than read the value atomically).

Simply reading is no good - at a minimum because the variable isn't marked as volatile, so subsequent reads may just reuse a cached value. Combining volatile & Interlocked would possibly work here (I'm not entirely sure, but I think an interlocked write cannot be seen in an inconsistent state even by another core doing a non-interlocked read). But if you do this you'll get a warning and a code smell for combining two different techniques.

Option 2: use a lock. Less desirable in this situation because the Interlocked approach is more performant in this case. But you can store the correct type, and it's marginally clearer:

DateTime _lastHit;
object _lock = new object();

void Touch()
{
    lock (_lock)
        _lastHit = DateTime.Now;
}

You must use a lock to read this value too! Incidentally, besides mutual exclusion a lock also ensures that cached values can't be seen and reads/writes can't be reordered.

Non-option: do nothing (just write the value), whether you mark it as volatile or not. This is wrong - even if you never read the value, your writes on a 32 bit machine may interleave in such an unlucky way that you get a corrupted value:

Thread1: writes dword 1 of value 1
Thread2: writes dword 1 of value 2
Thread2: writes dword 2 of value 2
Thread1: writes dword 2 of value 1

Result: dword 1 is for value 2, while dword 2 is for value 1
like image 121
Roman Starkov Avatar answered Oct 27 '22 16:10

Roman Starkov