10

In my application (written in C#) I have an instance of a class with a member variable that points to an instance of another class. This second instance is read-only, so the state of that instance will never change once it is created. But on some occasions I want to replace this instance with a new and updated instance. Is replacing this reference in the first instance safe in a multithreaded environment? Or can this cause a problem?

svb
  • 641
  • 1
  • 7
  • 13
  • 5
    [This answer](http://stackoverflow.com/a/2193445/493928) points that reference assignments are atomic. – khachik Mar 23 '12 at 14:00
  • 1
    Thread-safe doesn't mean _atomic_. One instruction can be atomic but not thread-safe. One thread-safe block of code may not be (and probably it won't be) atomic. – Adriano Repetti Mar 23 '12 at 15:05

6 Answers6

9

The simple write operation is in itself thread-safe.

So this is Ok:

x.y = new Y();   // OK

but the following is not:

if (x.y == null)
    x.y = new Y();   // might overwrite a non-null x.y

So it depends on how you want to use it, and what the threads expect. But the atomicity means you'll never have an invalid (half-written) reference in x.y

H H
  • 263,252
  • 30
  • 330
  • 514
9

Yes, it is "safe" in the sense that the reference read/write is atomic. Your client code will always get a valid instance.

Obviously you cannot guarantee that the client code will get the new instance immediately.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • 2
    "Obviously you cannot guarantee that the client code will get the new instance immediately" then how can you say it's thread-safe? It's even more dangerous than an exception! – Adriano Repetti Mar 23 '12 at 14:20
  • 7
    Because it may not matter if the code still has an old instance. Even with volatile the client code can cache or temporarily copy the reference. That kind of thread sync is outside the scope of the question. – GazTheDestroyer Mar 23 '12 at 14:27
  • 1
    It is inside the scope of the question. _Thread-safe_ at least means that the code does what it is intended to do. To call an event handler usually you **copy the instance to be thread safe** but if you access another object then you're **not** thread safe 'cause your program may do something unexpected (what if after assignment the object is disposed?). With volatile at least the compiler (or some dirty detail of CPU architecture) won't do that (so the user is responsible of that). – Adriano Repetti Mar 23 '12 at 14:36
  • From the question "This second instance is read-only, so the state of that instance will never change once it is created". As I stated in my answer, code may get old values, but it will not "blow up" because of a reference replacement. – GazTheDestroyer Mar 23 '12 at 14:42
  • 8
    The OP is basically asking if tearing can occur with reference assignments, in which the reference itself is pointing to garbage because it's not an atomic operation. That can't occur because .NET ensures that reference assignments are atomic. The OP says that he only cares that at any point in time the variable (from any thread) will point to a valid instance, even if it's an "old" instance. This answer confirms what the OP was hoping to hear. (+1) – Servy Mar 23 '12 at 14:59
  • @Servy I guess this is what he wanted to hear but we can't assert it's the truth. It depends what his code will do with that object. Whenever you can't assert what your code you'll do during the time then you have a big problem with your threads (do we want to call it bug?) – Adriano Repetti Mar 23 '12 at 15:26
  • The OP was asking if assigning a reference is atomic. He didn't use that exact word. Unfortunately he used the term 'thread safe' which [is not a well defined term](http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx). Through reading the rest of his post, and comments here, it's clear that, for his purposes, an atomic operation is 'thread safe'. If he just said, is this 'thread safe' and nothing else I would agree that saying it's atomic is not good enough, but he has given sufficient context to assert that atomicity is enough. – Servy Mar 23 '12 at 16:10
  • @Servy: The only comment I saw from the OP indicated that *staleness* is a non-issue. There are other ways naive logic in the page request code could go horribly wrong. – Brian Gideon Mar 23 '12 at 16:39
4

Is replacing this reference in the first instance safe in a multithreaded environment?

Sorry to be wishy-washy here, but it depends on how you define "safe".

  • It is safe in that it will not corrupt state. This is because reference assignments are atomic.
  • It is not safe in that it may lead to unintended behavior.

The unintended behavior can be divided into three broad categories.

  • A combination of a read followed by a write may lead to a race condition.
  • One thread may not observe changes made by another due to compiler and hardware optimizations.
  • One thread may observe a change between two different reads of the reference variable.

Case #1: So in the first case two threads could race to make an assignment under false pretenses. The best example of this is the creation of a singleton.

public static Singleton GetSingleton()
{
  // More than one thread could read null and attempt instantiation here.
  if (instance == null)
  {
    instance = new Singleton();
  }
  return instance;
} 

Case #2: In the second case one thread may continue to read the old instance even though a new instance was published by another thread. Consider this example.

public class Example
{
  private SuperHero hero = new Superman();

  void ThreadA()
  {
    while (true)
    {
      Thread.Sleep(1000);
      hero = new CaptainAmerica();
      Thread.Sleep(1000);
      hero = new GreenLantern();
      Thread.Sleep(1000);
      hero = new IronMan();
    }

  void ThreadB()
  {
    while (true)
    {
      hero.FightTheBadGuys();
    }
  }
}

The problem is that compiler or hardware might optimize ThreadB by "lifting" the read of hero outside of the loop like this.

void ThreadB()
{
  SuperHero local = hero;
  while (true)
  {
    local.FightTheBadGuys();
  }
}

It should not be hard to see that one of our superheros is going to get really tired. Then again, he is a superhero so there may not be a problem afterall. The point I am trying to make here is that this staleness artifact may or may not be a problem depending on your situation.

Case #3: In the third case a thread may incorrectly assume the variable will reference the same instance between two reads.

void SomeThread()
{
  var result1 = instance.DoSomething();
  // The variable instance could get changed here!
  var result2 = instance.DoSomethingElse();
  // Are result1 and result2 in a mutually consistent state here?
}

In the above example result1 and result2 may not be in a mutually consistent state. This is because they were derived from operations running on two different instances. Again, this may or may not be a problem depending on your specific scenario.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
3

Reference replacement is a 32bit integer write operation in 32bit environment and 64bit integer write in 64bit environment respectively. And write operation really performed in two steps:

  • Read a new value
  • Write a new value

I believe it is not thread safe by default

sll
  • 61,540
  • 22
  • 104
  • 156
2

No, it's not thread-safe because of cache (some architectures needs a memory barrier for something like this), for compiler optimizations and for concurrent access.
You have following choices:

  • Declare that field as volatile.
  • Use Interlocked.Exchange() to replace the value.

Read MSDN documentation to choose what's better for you (quick: volatile should be faster but you can't save the old value and write the new one in one atomic operation).

EDITED
I think I have to make clear what I mean (many people seem to consider atomic a synonym of thread-safe):

Is replacing this reference in the first instance safe in a multithreaded environment?

No, strictly speaking it's not thread-safe. You don't know what will happen so it can't be safe.

Or can this cause a problem?

It depends on the context. If one thread won't use the correct socket connection (or a closed one) then it'll be a big problem. If one thread won't use the right color for formatting its output then it may not be a problem.

Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • Your answering half the questions and being vague, "something like this" why not detail what you mean! – Lloyd Mar 23 '12 at 14:33
  • I don't have to save the old value and write the new one in one atomatic operation. I just want to replace the value. Does this make a difference? Volatile seems to prevent the compiler from making some optimizations. Is this really necessary if the threads that are reading the data don't need to see the change instantly? My application is a web application in .Net. In one request I want to change the value, but requests that are already being handled don't necessarily need see the new value. There is some error margin allowed in this case. – svb Mar 23 '12 at 14:36
  • @Lloyd you're right, **every** write operation is not thread-safe because the compiler (JIT too) can do optimizations and because of details of CPU architecture. – Adriano Repetti Mar 23 '12 at 14:55
  • @svb if you can't be sure of what will happen then you shouldn't do it. Do you want to take the risk of a subtle bug because of a 1 msec gain in performance (for a web application)? You can't know _when_ replacement will happen then you can't know what other threads will do. – Adriano Repetti Mar 23 '12 at 14:57
  • 1
    @svb: It's not even a matter of *seeing it instantly*. The other thread is free to continue reading the cached value *forever* since you've not explicitly told it not to. As noted, though, this may not necessarily be a problem for your application. – Edward Thomson Mar 23 '12 at 15:35
0

Use following code where you want to update the object with new instance. it is thread safe code.

MyClass obj = null;
Interlocked.CompareExchange(ref obj, new MyClass(), null);
Romil Kumar Jain
  • 20,239
  • 9
  • 63
  • 92