We need to lock a .NET Int32 when reading it in a multithreaded code?
.net, c#, locking, multithreading
Solution
Locking accomplishes two things:
- It acts as a mutex, so you can make sure only one thread modifies a set of values at a time.
- It provides memory barriers (acquire/release semantics) which ensures that memory writes made by one thread are visible in another.
Most people understand the first point, but not the second. Suppose you used the code in the question from two different threads, with one thread calling `Add` repeatedly and another thread calling `Read`. Atomicity on its own would ensure that you only ended up reading a multiple of 8 - and if there were two threads calling `Add` your lock would ensure that you didn't "lose" any additions. However, it's quite possible that your `Read` thread would only ever read 0, even after `Add` had been called several times. Without any memory barriers, the JIT could just cache the value in a register and assume it hadn't changed between reads. The point of a memory barrier is to either make sure something is really written to main memory, or really read from main memory.
Memory models can get pretty hairy, but if you follow the simple rule of taking out a lock every time you want to access shared data (for read or write) you'll be okay. See the volatility/atomicity part of my threading tutorial for more details.
Problem
I was reading the following article: http://msdn.microsoft.com/en-us/magazine/cc817398.aspx "Solving 11 Likely Problems In Your Multithreaded Code" by Joe Duffy And it raised me a question: "We need to lock a .NET Int32 when reading it in a multithreaded code?" I understand that if it was an Int64 in a 32-bit SO it could tear, as it is explained in the article. But for Int32 I imagined the following situation: ``` class Test { private int example = 0; private Object thisLock = new Object(); public void Add(int another) { lock(thisLock) { example += another; } } public int Read() { return example; } } ``` I don't see a reason to include a lock in the Read method. Do you? Update Based on the answers (by Jon Skeet and ctacke) I understand that the code above still vulnerable to multiprocessor caching (each processor has its own cache, unsynchronized with others). All the three modifications bellow fix the problem: - Adding to "int example" the "volatile" property - Inserting a Thread.MemoryBarrier(); before the actual reading of "int example" - Read "int example" inside a "lock(thisLock)" And I also think that "volatile" is the most elegant solution.