Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread synchronization. Why exactly this lock isn't enough to synchronize threads [duplicate]

Possible Duplicate:
Threads synchronization. How exactly lock makes access to memory 'correct'?

This question is inspired by this one.

We got a following test class

class Test
{
    private static object ms_Lock=new object();
    private static int ms_Sum = 0;

    public static void Main ()
    {
        Parallel.Invoke(HalfJob, HalfJob);
        Console.WriteLine(ms_Sum);
        Console.ReadLine();
    }

    private static void HalfJob()
    {
        for (int i = 0; i < 50000000; i++) {
            lock(ms_Lock) { }// empty lock

            ms_Sum += 1;
        }
    }
}

Actual result is very close to expected value 100 000 000 (50 000 000 x 2, since 2 loops are running at the same time), with around 600 - 200 difference (mistake is approx 0.0004% on my machine which is very low). No other way of synchronization can provide such way of approximation (its either a much bigger mistake % or its 100% correct)

We currently understand that such level of preciseness is because of program runs in the following way:

enter image description here

Time is running left to right, and 2 threads are represented by two rows.

where

  • black box represents process of acquiring, holding and releasing the

  • lock plus represents addition operation ( schema represents scale on my PC, lock takes approximated 20 times longer than add)

  • white box represents period that consists of try to acquire lock, and further awaiting for it to become available

Also lock provides full memory fence.

So the question now is: if above schema represents what is going on, what is the cause of such big error (now its big cause schema looks like very strong syncrhonization schema)? We could understand difference between 1-10 on boundaries, but its clearly no the only reason of error? We cannot see when writes to ms_Sum can happen at the same time, to cause the error.

EDIT: many people like to jump to quick conclusions. I know what synchronization is, and that above construct is not a real or close to good way to synchronize threads if we need correct result. Have some faith in poster or maybe read linked answer first. I don't need a way to synchronize 2 threads to perform additions in parallel, I am exploring this extravagant and yet efficient , compared to any possible and approximate alternative, synchronization construct (it does synchronize to some extent so its not meaningless like suggested)

like image 489
Valentin Kuzub Avatar asked Sep 01 '11 00:09

Valentin Kuzub


People also ask

Is lock required for a synchronized method?

Every class in Java has a unique lock which is nothing but class level lock. If a thread wants to execute a static synchronized method, then the thread requires a class level lock.

Why are locks better than synchronized?

Lock framework works like synchronized blocks except locks can be more sophisticated than Java's synchronized blocks. Locks allow more flexible structuring of synchronized code.

Can two threads acquire the same lock?

There is no such thing.

Can multiple threads take a lock simultaneously?

Only one thread can hold a lock at a time. If a thread tries to take a lock that is already held by another thread, then it must wait until the lock is released.


2 Answers

lock(ms_Lock) { } this is meaningless construct. lock gurantees exclusive execution of code inside it.

Let me explain why this empty lock decreases (but doesn't eliminate!) the chance of data corruption. Let's simplify threading model a bit:

  1. Thread executes one line of code at time slice.
  2. Thread scheduling is done in strict round robin manner (A-B-A-B).
  3. Monitor.Enter/Exit takes significantly longer to execute than arithmetics. (Let's say 3 times longer. I stuffed code with Nops that mean that previous line is still executing.)
  4. Real += takes 3 steps. I broke them down to atomic ones.

At left column shown which line is executed at the time slice of threads (A and B). At right column - the program (according to my model).

A   B           
1           1   SomeOperation();
    1       2   SomeOperation();
2           3   Monitor.Enter(ms_Lock);
    2       4   Nop();
3           5   Nop();
4           6   Monitor.Exit(ms_Lock);
5           7   Nop();
7           8   Nop();
8           9   int temp = ms_Sum;
    3       10  temp++;
9           11  ms_Sum = temp;                          
    4           
10              
    5           
11              

A   B           
1           1   SomeOperation();
    1       2   SomeOperation();
2           3   int temp = ms_Sum;
    2       4   temp++;
3           5   ms_Sum = temp;                          
    3           
4               
    4           
5               
    5           

As you see in first scenario thread B just can't catch thread A and A has enough time to finish execution of ms_Sum += 1;. In second scenario the ms_Sum += 1; is interleaved and causes constant data corruption. In reality thread scheduling is stochastic, but it means that thread A has more change to finish increment before another thread gets there.

like image 73
Andrey Avatar answered Nov 15 '22 07:11

Andrey


This is a very tight loop with not much going on inside it, so ms_Sum += 1 has a reasonable chance of being executed in "just the wrong moment" by the parallel threads.

Why would you ever write a code like this in practice?

Why not:

lock(ms_Lock) { 
    ms_Sum += 1;
}

or just:

Interlocked.Increment(ms_Sum);

?

-- EDIT ---

Some comments on why would you see the error despite memory barrier aspect of the lock... Imagine the following scenario:

  • Thread A enters the lock, leaves the lock and then is pre-empted by the OS scheduler.
  • Thread B enters and leaves the lock (possibly once, possibly more than once, possibly millions of times).
  • At that point the thread A is scheduled again.
  • Both A and B hit the ms_Sum += 1 at the same time, resulting in some increments being lost (because increment = load + add + store).
like image 35
Branko Dimitrijevic Avatar answered Nov 15 '22 08:11

Branko Dimitrijevic