Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I avoid using locks for my seldomly-changing variable?

I've been reading Joe Duffy's book on Concurrent programming. I have kind of an academic question about lockless threading.

First: I know that lockless threading is fraught with peril (if you don't believe me, read the sections in the book about memory model)

Nevertheless, I have a question: suppose I have an class with an int property on it.

The value referenced by this property will be read very frequently by multiple threads

It is extremely rare that the value will change, and when it does it will be a single thread that changes it.

If it does change while another operation that uses it is in flight, no one is going to lose a finger (the first thing anyone using it does is copy it to a local variable)

I could use locks (or a readerwriterlockslim to keep the reads concurrent). I could mark the variable volatile (lots of examples where this is done)

However, even volatile can impose a performance hit.

What if I use VolatileWrite when it changes, and leave the access normal for reads. Something like this:

public class MyClass
{
  private int _TheProperty;
  internal int TheProperty
  {
    get { return _TheProperty; }
    set { System.Threading.Thread.VolatileWrite(ref _TheProperty, value); }
  }
}

I don't think that I would ever try this in real life, but I'm curious about the answer (more than anything, as a checkpoint of whether I understand the memory model stuff I've been reading).

like image 463
JMarsch Avatar asked Jan 28 '10 21:01

JMarsch


People also ask

Why do we need locks?

Locks are used to guard a shared data variable, like the account balance shown here. If all accesses to a data variable are guarded (surrounded by a synchronized block) by the same lock object, then those accesses will be guaranteed to be atomic — uninterrupted by other threads.

Why are locks needed in a multi threaded program?

When you run a program with multithreading it is very hard to debug what happens when exactly and which thread has priority over other threads to access the shared resource. Here is where locks come into play and we use them to prevent those same deadlocks.

How does acquiring a lock prevent other threads from entering into the critical section?

A lock is also called a mutex or mutex lock because it provides mutually exclusive access to a critical section. That is, once a thread acquires a lock and enters a critical section, no other thread can also enter the critical section until the first thread completes its work.

What does lock () return when a the lock is being held?

The lock statement acquires the mutual-exclusion lock for a given object, executes a statement block, and then releases the lock. While a lock is held, the thread that holds the lock can again acquire and release the lock. Any other thread is blocked from acquiring the lock and waits until the lock is released.


3 Answers

Marking a variable as "volatile" has two effects.

1) Reads and writes have acquire and release semantics, so that reads and writes of other memory locations will not "move forwards and backwards in time" with respect to reads and writes of this memory location. (This is a simplification, but you take my point.)

2) The code generated by the jitter will not "cache" a value that seems to logically be unchanging.

Whether the former point is relevant in your scenario, I don't know; you've only described one memory location. Whether or not it is important that you have only volatile writes but not volatile reads is something that is up to you to decide.

But it seems to me that the latter point is quite relevant. If you have a spin lock on a non-volatile variable:

while(this.prop == 0) {}

the jitter is within its rights to generate this code as though you'd written

if (this.prop == 0) { while (true) {} }

Whether it actually does so or not, I don't know, but it has the right to. If what you want is for the code to actually re-check the property on each go round the loop, marking it as volatile is the right way to go.

like image 69
Eric Lippert Avatar answered Oct 17 '22 03:10

Eric Lippert


The question is whether the reading thread will ever see the change. It's not just a matter of whether it sees it immediately.

Frankly I've given up on trying to understand volatility - I know it doesn't mean quite what I thought it used to... but I also know that with no kind of memory barrier on the reading thread, you could be reading the same old data forever.

like image 20
Jon Skeet Avatar answered Oct 17 '22 02:10

Jon Skeet


The "performance hit" of volatile is because the compiler now generates code to actually check the value instead of optimizing that away - in other words, you'll have to take that performance hit regardless of what you do.

like image 44
Anon. Avatar answered Oct 17 '22 02:10

Anon.