Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this assignment not thread-safe?

I've been reading this book from Joseph Albahari about threading:
http://www.albahari.com/threading/

In Part 2, I found this example:
http://www.albahari.com/threading/part2.aspx#_When_to_Lock

Here is the aforementioned example:

class ThreadUnsafe
{
  static int _x;
  static void Increment() { _x++; }
  static void Assign()    { _x = 123; }
}

Thread-safe version:

class ThreadSafe
{
  static readonly object _locker = new object();
  static int _x;

  static void Increment() { lock (_locker) _x++; }
  static void Assign()    { lock (_locker) _x = 123; }
}

I couldn't understand why Assign method is not thread safe. Shouldn't integer assignment be atomic operation on both 32- and 64-bit architectures?

like image 631
Ivan Peric Avatar asked Mar 12 '14 11:03

Ivan Peric


People also ask

What does it mean for something to not be thread-safe?

Conditionally safe: Different threads can access different objects simultaneously, and access to shared data is protected from race conditions. Not thread safe: Data structures should not be accessed simultaneously by different threads.

Why are lists not thread-safe?

Being thread-safe would mean that any operation modifying the list would need to be interlocked against simultaneous access. This would be necessary even for those lists that will only ever be used by a single thread. That would be very inefficient. Save this answer.

How do I know if a class is thread-safe?

To test if the combination of two methods, a and b, is thread-safe, call them from two different threads. Put the complete test in a while loop iterating over all thread interleavings with the help from the class AllInterleavings from vmlens. Test if the result is either an after b or b after a.


1 Answers

The assignment is atomic in that any reading thread will either see 123 or the previous value - not some intermediate value. However, there's no guarantee that a thread will see the new value until there have been two memory barriers: a write memory barrier in the writing thread, and a read memory barrier in the reading thread.

If you had two threads like this (after making _x public or internal, so that it could be read from other types of course - or with the code in the ThreadSafe class anyway):

// Thread 1
Console.WriteLine("Writing thread starting");
ThreadSafe.Assign();
Console.WriteLine("Writing thread done");


// Thread 2
Console.WriteLine("Reading thread starting");
while (ThreadSafe._x != 123)
{
    // Do nothing
}
Console.WriteLine("Reading thread done");

... there's no guarantee that thread 2 would ever finish, because thread 2 might not "see" the assignment from thread 1.

like image 69
Jon Skeet Avatar answered Oct 19 '22 23:10

Jon Skeet