views:

648

answers:

3

I've been doing simple multi-threading in VB.NET for a while, and have just gotten into my first large multi-threaded project. I've always done everything using the Synclock statement because I didn't think there was a better way.

I just learned about the Interlocked Class - it makes it look as though all this:

Private SomeInt as Integer
Private SomeInt_LockObject as New Object

Public Sub IntrementSomeInt
    Synclock SomeInt_LockObject
        SomeInt += 1
    End Synclock
End Sub

Can be replaced with a single statement:

Interlocked.Increment(SomeInt)

This handles all the locking internally and modifies the number. This would be much simpler than writing my own locks for simple operations (longer-running or more complicated operations obviously still need their own locking).

Is there a reason why I'd rolling my own locking, using dedicated locking objects, when I can accomplish the same thing using the Interlocked methods?

+4  A: 

You're correct; Interlocked should be used here, and will be faster than SyncLock.
However, the Interlocked class is not well-known.

However, there are situations where you need to use SyncLock and Interlocked will not help.

SLaks
Can you provide an example of such a situation? If I'm just doing a simple increment/decrement (or .Add if I need to change the value by more than 1), it seems like Interlocked would be better in all cases. If I need something more advanced (like a 4-step process where I need to guarantee it's the only thread running it at any given time), I need some Synclocking. I'm interested in a situation where Interlocked would appear to be proper, but is a bad choice.
rwmnau
If you're working with two different variables, `Interlocked` is not enough.
SLaks
+2  A: 

This is because no one knows about it. Spread the word!

ChaosPandion
+1 Good answer.
SLaks
That's what I thought - I see that it's been around since 1.0, and so I was astounded when I just found it. I thought "There has to be something else going on here that I'm not seeing, or else this method of locking would be much more popular". I really just want to make sure it's as great is it appears to be on first glance.
rwmnau
+1  A: 

The short answer is because using a Monitor lock (SyncLock in VB and lock { } in C#) not only assures that only one thread at a time can access the variable (or, in a strict sense, only one thread at a time can obtain a lock on the locking object), but it also creates the memory barrier required to ensure that reads on the variable aren't optimized away.

If you're never simply reading the value of the variable (in other words, all of your work is done through calls to Interlocked), then you'll be OK. However, if you need to be able to perform a normal read of the variable then the situation is more complicated. Lockless reads/writes are usually accomplished in C# using the volatile keyword. This instructs the compiler to read the value of the variable everywhere it's used, rather than optimizing away any of these reads into a local cache. Unfortunately there is no equivalent in VB.NET, so you'll have to use something else.

The accepted answer to this question should provide some more information on what you can do. In short, most people use SyncLock in VB.NET because it's easier and less complicated than the logic required to do it without SyncLock.

Adam Robinson
Not entirely correct, when you change your variables with Interlocked, you can safely read them without any special measures. Because the barrier is around the writes, the reads are safe.
Henk Holterman
@Henk: Couldn't multiple reads that are subsequent to the `Interlocked` call be optimized into a single cached read?
Adam Robinson
Adam, yes they could. And from a real-time perspective that might seem 'wrong' but it would not lead to incorrect behaviour. Check the Interlocked members. There only is a Read for 64 bit values (on 32 bit systems). http://msdn.microsoft.com/en-us/library/system.threading.interlocked_members.aspx
Henk Holterman
@Henk: How would it not be incorrect behavior if another thread updated the value after it had been cached?
Adam Robinson
Funny stuff, hey? Because what if that read had happened a millisecond earlier or later, would it have been 'incorrect' then? Note that Interlocked.Increment() does its own read for just this reason.
Henk Holterman
@Henk: Correctness in terms of output, then no, it wouldn't have been "incorrect". In terms of behavior, then yes, that would be "incorrect". You said that the fact that read optimizations subsequent to the `Interlocked` call could be optimized out "would not lead to incorrect behavior". The very existence of volatile variables (and the problem that they address) seems to contradict what you're saying.
Adam Robinson
volatile and Interlocked address the same problem, on different levels. Note that `volatile int x; x = x + 1;` is not thread-safe.
Henk Holterman
No, they don't. Interlocked allows for a single-step execution and prevention of the CPU from interrupting the internal multi-step process. There is a memory barrier created by the functions in the `Interlocked` class, but any actions outside of it (i.e. a normal use of the variable) is not going to be given the same protection.
Adam Robinson
And you're correct, `x = x + 1` is not guaranteed to execute uninterrupted. It is, however, guaranteed that whenever `x` is used later in the procedure that the variable is actually read and not cached.
Adam Robinson
Adam, try to create a concrete scenario where caching Interlocked vars matters.
Henk Holterman
@Henk: Two threads executing a repetitive action in a loop. For each iteration, the procedure uses `Interlocked.Increment` to increment a counter, storing the return value in a local variable. The thread executes code, then uses a spinwait with a `Thread.Sleep(0)` call in a `while` loop until the counter has been incremented again (in other words, it processes once, then waits for the second thread to do the same)
Adam Robinson
In such a case, there is the potential for the variable to be read only once after the `Increment` call. Unless the other thread processes its action between the call to `Increment` and this read, the thread will wait forever, which will then cause the *other* thread to wait forever.
Adam Robinson