Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

System.Threading.Interlocked - The theory

253 views
Skip to first unread message

Lee Chapman

unread,
Nov 19, 2002, 7:19:12 AM11/19/02
to
Can somebody please sanity-check my understanding of
System.Threading.Interlocked?

Interlocked methods are atomic, which means that the operation is
protected against the side effects of time slicing in a multitasking
operating system. However, there don't appear to be any Interlocked
methods to atomically just read a variable. I'm assuming that this is
because such operations are intrinsically atomic (with the data types
supported by the Interlocked class' methods).

Does this hold up on a truely concurrent system (i.e. one with multiple
processors)? Is the following (rather contrived) C# code thread-safe (or
not, depending on the comments)?

class Test
{
public Test()
{
_int = 5;

// I assume that thread-safe code isn't needed in
// the constructor as it's not possible for more
// than one thread to access this instance before
// it's constructor has finished? (We're not
// accessing any static members.)
}

private int _int;
private int _int2;
private int _int3;

public int Method1()
{
return _int;

// Intrinsically thread-safe because _int is only
// ever written to using Interlocked methods?
}

public int Method2(int value)
{
int ret = Interlocked.Exchange(ref _int, value);

return ret;

// Thread-safe.
}

public bool Method3(int x)
{
// Again, instrinsically thread-safe?

if (_int > x)
return true;

return false;
}

public int Method4(int value)
{
_int2 = value;
return _int2;

// Not thread-safe.
}

public int Method5(int value)
{
lock (this)
{
_int2 = value;
return _int2;
}

// Thread-safe (assuming that Method4() didn't exist).
// Not thread-safe if Method4() does exist.
}

public int Method6a(int value)
{
return Interlocked.Exchange(ref _int3, value);
}

public int Method6b(int value)
{
lock (this)
{
_int3 = value;
return _int3;
}

// Not thread-safe, because _int3 is also accessed using
// Interlocked methods in Method6a().
}

protected int _int4;

public void Method7()
{
Interlocked.Increment(ref _int4);

// Not thread-safe unless all derived classes also only
// write to _int4 using Interlocked methods.
}
}

Also, can anyone think of a way of implementing an atomic boolean (so
that an instance of it can be used anywhere a System.Boolean instance
can)? Or is System.Boolean effectively thread-safe (in the sense that a
bool will always evaluate to either true or false if there are multiple
reading threads and only one writing thread; what about multiple readers
& writers)?

Thanks,
- Lee


Steve

unread,
Nov 19, 2002, 10:41:25 AM11/19/02
to
I am not entirely convinced that your Method1 is correct,
although you may be correct. I understand your reasoning,
but the issue I have is that reading a variable, except
for booleans, is not atomic. You could be in the middle of
reading a variable and then other atomic writes
(interlocked) could adversley effect the variable for
which you have started reading in the first place anyways.
Again, I'd be careful but I'm not sure in this scenario. I
would simply do this.

object obj_int = (object)_int;
public int Method1()
{
lock(obj_int)
{
int status = _int;
}
return status;
}

In Method2, be aware that the ret is the previous value of
_int (before the Exchange).

public int Method2(int value)
{
int ret = Interlocked.Exchange(ref _int, value);

return ret;

// Thread-safe.
}

Again for the same reasons as above Method3 should be

public bool Method3(int x)
{
bool status = false;
lock(obj_int)
{
if (_int > x)
status = true;
}
return status;
}


Method5 may be VERY BAD :-)

public int Method5(int value)
{
lock (this)
{
_int2 = value;
return _int2;
}

// Thread-safe (assuming that Method4() didn't
exist).
// Not thread-safe if Method4() does exist.
}

Here you return, but the finally block (internal to lock)
gets executed anyways, I think. It could be that the lock
is never released (a bad situation). Unclear, why not make
the code cleaner juat in case. Better to write

public int Method5(int value)
{
int status = 0;


lock (this)
{
_int2 = value;

status = value;
}
return status;
}


Steve

>.
>

Lee Chapman

unread,
Nov 19, 2002, 11:53:33 AM11/19/02
to
Thanks for contributing, but I want to check that I'm using Interlocked
correctly - I'm not after alternatives.

> [...] the issue I have is that reading a variable, except


> for booleans, is not atomic.

Why is reading/writing a boolean any different to any other data type?
(I'm not suggesting that it is or isn't; I'm just wondering what your
evidence/reasoning is.)

> You could be in the middle of
> reading a variable and then other atomic writes
> (interlocked) could adversley effect the variable for
> which you have started reading in the first place anyways.

That is the crux of the problem, according to my understanding, which is
why I asked if the absence of any Interlocked methods to just read the
variable meant that reading such variables was intrinsically atomic.

> Method5 may be VERY BAD :-)

> [...]


> Here you return, but the finally block (internal to lock)
> gets executed anyways, I think. It could be that the lock
> is never released (a bad situation). Unclear, why not make
> the code cleaner juat in case.

Sorry, but I have to totally disagree with you here. There is nothing
ambiguous with the original implementation. The C# lock keyword is there
precisely to guarantee that the lock will always been released when flow
moves out of the following statement block; this will happen whether
flow exits premuaturely due to an exception or a return statement or
otherwise.

- Lee


Dev Eloper

unread,
Nov 19, 2002, 12:25:18 PM11/19/02
to
> ...in the middle of reading a variable...

Remember that reading _int is one (1) 32-bit operation, it's not
4 separate Bytes being read. This no lock is needed when reading.
If someone updates _int almost at the same time as you are
reading, you will get either the old or the new value, not
something in between.


Not sure about this, maybe somone can fill in? But I think you need
to declare the _int as 'volatile'. If not, I think you can get the
situation where CPU-0 does an update, but CPU-1 has _int in a register
so the thread scheduled on CPU-1 never sees the update. Am I right?

If you used a monitor (lock) to protect _int (needed when both
reading and writing), I don't think you need to declare it as
volatile, since that will be taken care of by the compiler.
(But interlocked is of course better than lock)

(*If* you lock with a monitor (like Steve's sample) you also need
to do something about method 2, since during the lock, method 2
still can update _int. However, you would proably not both use
interlocked and a monitor to lock)


In the constructor, only one thread can be using the object being
constructed, so you're right in that no sync is needed.


(Don't have time to comment more now... I might comment more later)


"Steve" <sdona...@farabi.com> wrote in message news:90eb01c28fe2$1e743bb0$8df82ecf@TK2MSFTNGXA02...

Brian Gideon

unread,
Nov 19, 2002, 12:58:59 PM11/19/02
to
Lee,

You were using the lock correctly. In fact, I would not recommend the
alternative provided by Steve. That is because someone may later modify the
code and unwisely do something like...

public int Method5(int value)
{
lock (this)
{
_int2 = value;
}

return _int2 * _int2 + _int2;
}

...which is obviously not thread-safe.

Brian

Mickey Williams

unread,
Nov 19, 2002, 1:28:52 PM11/19/02
to
> However, if reading _int is a single 32-bit operation then isn't writing
> _int also a single 32-bit operation? In which case, if you're going to
> throw away the return value from Interlocked.Exchange(), isn't that just
> the same as writing to the variable directly?

Reading or writing native word sizes or smaller is atomic. See section
11.6.6 in Partition I of the CIL spec. Updating (reading, modifying, then
storing) is not guaranteed to be atomic.

--
Mickey Williams
Author, "Microsoft Visual C# .NET", Microsoft Press


Mickey Williams

unread,
Nov 19, 2002, 1:31:01 PM11/19/02
to
"Dev Eloper" <do...@not.matter.ch> wrote in message
news:e1aUjC$jCHA.3808@tkmsftngp08...

> Not sure about this, maybe somone can fill in? But I think you need
> to declare the _int as 'volatile'. If not, I think you can get the
> situation where CPU-0 does an update, but CPU-1 has _int in a register
> so the thread scheduled on CPU-1 never sees the update. Am I right?
>

The volatile keyword doesn't guarantee atomicity, but it does guarantee
visibility of read/writes with respect to ordering across threads.

Lee Chapman

unread,
Nov 19, 2002, 1:03:42 PM11/19/02
to
> Remember that reading _int is one (1) 32-bit operation, it's not
> 4 separate Bytes being read. This no lock is needed when reading.
> If someone updates _int almost at the same time as you are
> reading, you will get either the old or the new value, not
> something in between.

That sounds promising. So sure basically saying, "yes, there are no
Interlocked methods to just read a variable because such operations are
indeed instrinsically atomic"?

However, if reading _int is a single 32-bit operation then isn't writing
_int also a single 32-bit operation? In which case, if you're going to
throw away the return value from Interlocked.Exchange(), isn't that just
the same as writing to the variable directly?

i.e. Isn't

_int = 10;

the same as

Interlocked.Exchange(ref _int, 10); // throw away return value

(because we're not reading the value of _int before or after the
assignment)?

In fact you've just made me answer one of my original questions. The
.NET documentation clearly states: "this type is safe for multithreaded
operations" under the System.Boolean data type, so I assume that I can
use a C# bool in a multi-threaded, multi-processor environment without
worrying about synchronization. Even System.Decimal appears to be
thread-safe too. As is System.Int32. But System.Single isn't.

Hold on, if System.Single (C# float) is not thread-safe, then how do I
read a float that is being manipulated using Interlocked.Exchange(ref
float, float) or Interlocked.CompareExchange(ref float, float, float)?

> Not sure about this, maybe somone can fill in? But I think you need
> to declare the _int as 'volatile'.

Good point - something I'd completely forgotten about. The .NET
documentation backs up your hunch: "The volatile modifier is usually
used for a field that is accessed by multiple threads without using the
lock statement to serialize access. Using the volatile modifier ensures
that one thread retrieves the most up-to-date value written by another
thread."

The documentation goes on to state:

The type of a field marked as volatile is restricted to the following
types:
Any reference type.
Any pointer type (in an unsafe context).
The types sbyte, byte, short, ushort, int, uint, char, float, bool.
An enum type with an enum base type of byte, sbyte, short, ushort,
int, or uint.

To me, this contradicts the bit that says System.Single isn't
thread-safe, because above it clearly implies that you can access a
float without the lock statement and mark it as volatile.

> If you used a monitor (lock) to protect _int (needed when both
> reading and writing), I don't think you need to declare it as
> volatile, since that will be taken care of by the compiler.
> (But interlocked is of course better than lock)

I can't find anything in the documentation that explicity states this,
but it is kind of implied.

> [...]

Everything else sounds right to me.

- Lee


Lee Chapman

unread,
Nov 19, 2002, 2:07:39 PM11/19/02
to
> int is 4 bytes and I think a native word size is 2 bytes.

Not unless you're running a 16-bit machine...

- Lee


Dev Eloper

unread,
Nov 19, 2002, 4:36:22 PM11/19/02
to
Great! :)

Have you considered writing an article about this and put on
Codeproject (http://www.codeproject.com)?

"Lee Chapman" <Please reply to group> wrote in message news:eHj#kvAkCHA.1280@tkmsftngp12...
>
> "Lee Chapman" <Please reply to group> wrote in message
> news:#K16GY8jCHA.1688@tkmsftngp08...


> > Can somebody please sanity-check my understanding of
> > System.Threading.Interlocked?
>

> Thanks go to everyone who chipped in on this one. I now understand when
> I need to synchronize my C# code in a multi-threaded environment. I
> think. ;-)
>
> 1. Atomicity
>
> On a 32-bit system, reading and writing data types that fit into 32-bits
> is atomic. Reading and writing larger data types is not; neither is
> updating any data type. (Where "updating" means any operation where the
> new value is based on the old one.)
>
> However, there are some update operations I can do atomically: those
> provided by the System.Threading.Interlocked class.
>
> Atomic operations are thread-safe. There is no need to use
> synchronization techniques around a single atomic operation.
>
> 2. Critical Sections
>
> Multiple statements can be put into a critical section by using the C#
> lock keyword (which is implemented by the compiler using the
> System.Threading.Monitor class).
>
> A critical section ensures that only one thread at a time will execute
> the code in the critical section. Other threads that reach the lock
> before the first thread has exited will queue to wait their turn. We can
> prevent two or more distinct sections of code from executing
> concurrently by locking them against the same object instance.
> Conversely, critical sections locked against different object instances
> may run concurrently.
>
> Putting code in a critical section does not make it atomic. If critical
> sections are used such that mutliple threads could still concurrently
> read, write or update the same piece of data then either: (a) all read,
> write or update operations must be atomic, which will only ensure the
> data is not corrupted; or (b) all access to the data in question must be
> synchronized (e.g. by using critical sections locked against the same
> object instance).
>
> 3. Volatile Data
>
> Using atomic operations does not guarantee that the result of those
> operations will become apparent to other threads in the same order in
> which they occurred in the thread that executed them.
>
> For example, if one thread executes
>
> returnData = 100;
> isDone = true;
>
> and a second is executing
>
> while (!isDone)
> ;
>
> int result = returnData;
>
> There is no guarantee that the change to returnData will become apparent
> to the second thread before the change to isDone does. However, by
> marking isDone as volatile, we can ensure that this is the case.
>
> 4. Constructors
>
> Code inside constructors does not need to be synchronized as a second
> thread cannot possibly touch the object until the constructor has
> returned.
>
> - Lee
>
>


Dev Eloper

unread,
Nov 19, 2002, 4:53:34 PM11/19/02
to

"Mickey Williams" <m...@codevtech.com> wrote in message news:eth0eo$jCHA.2596@tkmsftngp10...

> "Dev Eloper" <do...@not.matter.ch> wrote in message
> news:e1aUjC$jCHA.3808@tkmsftngp08...
>
> > Not sure about this, maybe somone can fill in? But I think you need
> > to declare the _int as 'volatile'. If not, I think you can get the
> > situation where CPU-0 does an update, but CPU-1 has _int in a register
> > so the thread scheduled on CPU-1 never sees the update. Am I right?
> >
>
> The volatile keyword doesn't guarantee atomicity, but it does guarantee
> visibility of read/writes with respect to ordering across threads.

Thanks, that was my understanding.


A related question... Suppose I have something like

class UsedByManyThreads
{
int m_integer = 0;
public void Foo()
{
lock (this)
{
m_integer++;
}
}
public int TheInt
{
get { return m_integer; }
}
}

Within the critical section in Foo(), my understanding is that if I change
the value of m_integer that will be visible across all CPUs, so I don't
need to make m_integer volatile when used within a critical section. Is
my assumption correct?


Lee Chapman

unread,
Nov 19, 2002, 3:39:17 PM11/19/02
to

"Lee Chapman" <Please reply to group> wrote in message
news:#K16GY8jCHA.1688@tkmsftngp08...
> Can somebody please sanity-check my understanding of
> System.Threading.Interlocked?

Thanks go to everyone who chipped in on this one. I now understand when

Brian Gideon

unread,
Nov 20, 2002, 5:18:31 PM11/20/02
to
Lee,

I believe you have summarized everything correctly. One thing I should add
to item 4 is that constructors do not need to be synchronized as long as
they don't access static variables. And another thing, according to Jeffrey
Richter's book "Applied .NET Framework Programming"...

"Version 1 of the Microsoft .NET Framework doesn't support volatile fields.
However, a future version will."

Brian

"Lee Chapman" <Please reply to group> wrote in message

news:eHj#kvAkCHA.1280@tkmsftngp12...

Shri Borde [MS]

unread,
Nov 20, 2002, 5:24:37 PM11/20/02
to
The Architecture doc states the following. The System.Threading.Interlocked
functions should only be needed for doing complex operations like increment
in an atomic fashion. Simple reads and writes will be automatic.

Atomic Reads and Writes

A conforming CLI shall guarantee that read and write access to properly
aligned memory locations no larger than the native word size (the size of
type native int) is atomic (see clause 11.6.2). Atomic writes shall alter no
bits other than those written. Unless explicit layout control (see Partition
II (Controlling Instance Layout)) is used to alter the default behavior,
data elements no larger than the natural word size (the size of a native
int) shall be properly aligned. Object references shall be treated as though
they are stored in the native word size.

Note: There is no guarantee about atomic update (read-modify-write) of
memory, except for methods provided for that purpose as part of the class
library (see Partition IV). An atomic write of a "small data item" (an item
no larger than the native word size) is required to do an atomic
read/write/modify on hardware that does not support direct writes to small
data items.

Note: There is no guaranteed atomic access to 8-byte data when the size of a
native int is 32 bits even though some implementations may perform atomic
operations when the data is aligned on an 8-byte boundary.

"Lee Chapman" <Please reply to group> wrote in message
news:#K16GY8jCHA.1688@tkmsftngp08...

Dev Eloper

unread,
Nov 21, 2002, 12:33:23 PM11/21/02
to

> "Version 1 of the Microsoft .NET Framework doesn't support volatile fields.
> However, a future version will."

I wonder if that's true?? Then the entire interlocked class would be useless,
right? It seems to work with v1 (service pack 2). Anyone know more details?


Willy Denoyette [MVP]

unread,
Nov 21, 2002, 2:03:33 PM11/21/02
to

> "Version 1 of the Microsoft .NET Framework doesn't support volatile fields.
> However, a future version will."

Volatile is supported as from v1.0, see the volatile keyword in C#.

public volatile int someIntValue;

Note however that Volatile doesn't imply read-modify-write semantic it only ensures proper ordering for read/write memory access.

Willy.

Victor Vatamanescu

unread,
Nov 26, 2002, 8:09:49 AM11/26/02
to
the .net framework on a 16-bit machine?!

"Lee Chapman" <Please reply to group> wrote in message

news:Ox3m28$jCHA.1988@tkmsftngp08...

0 new messages