Is a lock required if...

  • Thread starter Thread starter Invalid
  • Start date Start date
I

Invalid

I launch a worker thread that periodically reads a volatile bool
abortRequested, which could be set to true by my main form.
IOW, there is one thread that can read that bool and one different
thread that can set it. See code below for an overview.

In a C or C++ environment, my current approach would be
adequate (no need to acquire exclusive access to abortRequested).
I'm guessing that I'm still OK under C# because bool is a value
type. Correct?


public abstract class ISimpleWorkerThread
{
private volatile bool abortRequested;

public ISimpleWorkerThread()
{
abortRequested = false;
}

public void Start()
{
Thread t = new Thread(new ThreadStart(DoStuff));
t.Start();
}

public abstract void DoStuff() // Polls AbortRequested

protected bool AbortRequested
{
get {return abortRequested;}
}

public void Abort()
{
abortRequested = true;
}
}
 
Invalid said:
I launch a worker thread that periodically reads a volatile bool
abortRequested, which could be set to true by my main form.
IOW, there is one thread that can read that bool and one different
thread that can set it. See code below for an overview.

In a C or C++ environment, my current approach would be
adequate (no need to acquire exclusive access to abortRequested).
I'm guessing that I'm still OK under C# because bool is a value
type. Correct?

No - you're okay because the variable is volatile. If it weren't
volatile, you'd have to lock.
 
Jon Skeet said:
No - you're okay because the variable is volatile.

Thanks for the reply.

FWIW, I was thinking in terms of a simple read or write of a value type
being inherently atomic and thus thread-safe IF the variable is marked
volatile. I'm assuming that you could still get burned by a volatile object
if, say, one of its accessors needs to read or write multiple fields and
thus could be interrupted (?)
If it weren't volatile, you'd have to lock.

Well I have seen code that does a lock(this) and then manipulates
fields which aren't marked as volatile. I was actually surprised by
that, and I'm glad this has come up. Does locking on an object
cause it and only it to be treated in a volatile like manner, or are all
objects/values treated in a volatile like manner within a lock block?
Put another way, can you lock on one object and manipulate a
second object as long as the second object is only manipulated
when the first is locked? Example:

public class foo
{
SomeObject o1;
SomeOtherObject o2;

public void Test()
{
lock(o1)
{
if(o2.nonVolInt != 4)
o2.nonVolInt++;
}
}
}

In the above example, are we guaranteed that the memory
location associated with o2.nonVolInt is manipulated rather
than a register copy?
 
Invalid said:
Thanks for the reply.

FWIW, I was thinking in terms of a simple read or write of a value type
being inherently atomic and thus thread-safe IF the variable is marked
volatile. I'm assuming that you could still get burned by a volatile object
if, say, one of its accessors needs to read or write multiple fields and
thus could be interrupted (?)

Yes indeed. Also you still have problems if you do something like:

volatileInt++;
Well I have seen code that does a lock(this) and then manipulates
fields which aren't marked as volatile.

I personally prefer not locking on "this", but that's a different
discussion (and one already going in another thread: "What's wrong with
lock(this)").
I was actually surprised by
that, and I'm glad this has come up. Does locking on an object
cause it and only it to be treated in a volatile like manner, or are all
objects/values treated in a volatile like manner within a lock block?

What happens is the when you enter a lock, it performs a volatile read.
That means that the compiler can't do anything to "move" a read which
is after the lock entry in the program code to before the lock entry.
It's a sort of memory barrier - sort of an "ignore any cached copies of
anything you've got" operation.

When you exit a lock, it effectively performs a volatile write, which
means that the compiler can't do anything to "move" a write which is
before the lock exit in the program code to after the lock exit. This
is a different type of memory barrier - a sort of "flush any write
operations you've got in your cache" operation.

Between them, and with the action of a lock preventing another thread
from entering the same lock, it means that so long as you lock on the
same reference before accessing/modifying a shared piece of memory,
you're okay. However, you can't lock on one reference in one place and
another reference in a different place - then you get the memory
barrier effects, but not the exclusion, so nastiness can still happen.
Put another way, can you lock on one object and manipulate a
second object as long as the second object is only manipulated
when the first is locked?

Spot on.
Example:

public class foo
{
SomeObject o1;
SomeOtherObject o2;

public void Test()
{
lock(o1)
{
if(o2.nonVolInt != 4)
o2.nonVolInt++;
}
}
}

In the above example, are we guaranteed that the memory
location associated with o2.nonVolInt is manipulated rather
than a register copy?

Absolutely - exception that o1 is null rather than a valid reference,
so you'd want:

SomeObject o1 = new SomeObject();

In fact, a register copy *could* be modified within the lock, but then
flushed during the exit of the monitor. However, you'll perform a
"real" read of the value of o2.nonVolInt first.
 
Back
Top