Royi Namir
Royi Namir

Reputation: 148524

locking only 1 operation?

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

Answers (4)

Heinzi
Heinzi

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

zmbq
zmbq

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

Martin
Martin

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

Mahmoud Al-Qudsi
Mahmoud Al-Qudsi

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

Related Questions