Reputation: 148524
ive been asking myself : "why should i use lock to only one statement"...
(IMHO - if its 1 operation only like an assignment - so there shouldnt be a problem..)?
then i saw this :
As a basic rule, you need to lock around accessing any writable shared field. Even in the simplest case—an assignment operation on a single field—you must consider synchronization. In the following class, neither the Increment nor the Assign method is thread-safe:
class ThreadUnsafe
{
static int _x;
static void Increment() { _x++; }
static void Assign() { _x = 123; }
}
can you please tell me why this is not thread safe ? ive been running many scripts in my head and couldnt find any problem...
Upvotes: 5
Views: 229
Reputation: 172230
Here's an example of why your example is not thread-safe. Initially, _x = 0
. Let's say you run Increment
and Assign
in parallel. If the methods were thread-safe, the result should be either 100
(if increment is executed before assign) or 101
(if increment is executed after assign).
(EDIT: Note that each thread has it's own working stack!)
Thread 1 (executing Increment) Thread 2 (executing Assign 100)
-----------------------------------------------------------------
read _x onto stack (= 0)
put 100 on top of stack
write top of stack to _x (= 100)
increment top of stack (= 1)
write top of stack to _x (= 1)
_x
is now 1
, which is neither 100
nor 101
.
Of course, it could be that your incrementation method is compiled into single, atomic operation by the compiler. But you cannot rely on this, unless it is specifically guaranteed by the compiler that you use.
If you use a lock, the following happens:
Thread 1 (executing Increment) Thread 2 (executing Assign 100)
-----------------------------------------------------------------
lock (success)
read _x onto stack (= 0)
lock (lock already taken;
| wait until Thead 1's lock is released)
increment top of stack (= 1) |
write top of stack to _x (= 1) |
unlock |
+> (success)
put 100 on top of stack
write top of stack to _x (= 100)
unlock
The result is now 100
. Basically, the lock ensures that two locked blocks do not overlap.
Upvotes: 4
Reputation: 39013
Locking is a big messy subject, you will usually have a very hard time figuring out what goes under the hood (which core cache gets invalidated when). That's why writing efficient parallel code is a problem. Others have pointed out some potential issues even with a single assignment (and obviously with incrementing a variable). Just see all the issues with volatile keyword: https://www.google.com/search?q=.net+volatile+concurrency&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a
So, if you must do things in parallel, start by locking a lot, even on operations you don't think require locks. Optimize your locking only when you're seeing performance issues.
Upvotes: 0
Reputation: 5452
The increment operation produces this MSIL...
.method private hidebysig static void Increment() cil managed
{
// Code size 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: ldsfld int32 ThreadUnsafe::_x
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: stsfld int32 ThreadUnsafe::_x
IL_000d: ret
} // end of method ThreadUnsafe::Increment
So you can see that, even at the MSIL level, the increment is not atomic. The JIT compiler might conceivably do something clever to turn this back into an atomic increment at the machine level, but we certainly can't depend on that. Imagine 2 threads incrementing the same X with their "load" and "store" operations overlapped - you can see that it's possible to end up with X = X + 1 instead of X + 2.
Wrapping your increment inside a lock means they can't overlap.
Upvotes: 4
Reputation: 29529
You have to think at an even lower level than the programming language.
There's no guarantee that
a) The processor will write the new value all in one go (atomic or non-atomic)
b) The value will be updated in one CPU core's cache, but not in another (lack of memory barriers)
Maybe your CPU (likely) can read and write a 32-bit integer atomically and you won't have any problems. But what happens when you're trying to read/write a 64-bit value? A 128? The value could end up in an intdeterminate state where two different threads are simultaneously modifying the same memory location, and you end up with either value a, value b, or an intermediate (and very much incorrect) value that's a mix of the two.
and many more.
Upvotes: 1