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...
< Operating System Design. Wikipedia has related information at Lock (computer science) Locks are methods of synchronization used to prevent multiple threads from accessing a resource at the same time. Usually, they are advisory locks, meaning that each thread must cooperate in gaining and releasing locks.
The two types are exclusive and shared locks. Exclusive locks can be active or retained; shared locks can only be active (see Active and retained states for locks ). Note that there are no delete locks in RLS mode.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With