Concurrency using static variables

In the code below, if a call GetNextNumber()is called by two threads at the same time, is it possible that it returns the same number for both threads?

class Counter
{
 private static int s_Number = 0;
 public static int GetNextNumber()
 {
  s_Number++;
  return s_Number;
 }
}

Can you explain why?

EDIT: if the code can return the same number in both threads, then the following is true: Let it be said that two threads call GetNextNumber()when it s_Numberis 2. If the same value is returned, then this value can only be 4. It cannot be 3 . Is it correct?

+5
source share
4 answers

When working with such a simple counter, it is better to use Interlocked.Increment:

private static int s_Number = 0;

public static int GetNextNumber()
{
    return Interlocked.Increment(ref s_Number);
}

, ( ) .

:

  • s_Number
  • 1
  • s_Number
  • s_Number

:

  • 1 , , , 1, .
  • 1 3 , 4 , , .

, , lock :

private readonly object _SomeLock = new object();

...

lock (_SomeLock)
{
    // only 1 thread allowed in here at any one time
    // manipulate the data structures here
}

, , , - , Interlocked.Increment , .

Interlocked, , .

.

, s_Number 0 :

Thread 1                            Thread 2
Read s_Number = 0
                                    Read s_Number = 0
Add 1 to s_Number, getting 1
                                    Add 1 to s_Number, getting 1 (same as thread 1)
Store into s_Number (now 1)
                                    Store into s_Number (now 1)
Read s_Number = 1
                                    Read s_Number = 1
Return read value (1)
                                    Return read value (1)

, s_Number 2, 1, 2. 1, 1. .

Thread 1                            Thread 2
Read s_Number = 0
Add 1 to s_Number, getting 1
Store into s_Number (now 1)
                                    Read s_Number = 1
                                    Add 1 to s_Number, getting 2
                                    Store into s_Number (now 2)
Read s_Number = 2
                                    Read s_Number = 2
Return read value (2)
                                    Return read value (2)

s_Number 2, , 1, 2.

, IL. IL

// public static int GetNumber()
// {
GetNumber:
//     s_Number++;
IL_0000:  ldsfld      UserQuery.s_Number    // step 1: Read s_Number
IL_0005:  ldc.i4.1                          // step 2: Add 1 to it
IL_0006:  add                               //         (part of step 2)
IL_0007:  stsfld      UserQuery.s_Number    // step 3: Store into s_Number
// return s_Number;
IL_000C:  ldsfld      UserQuery.s_Number    // step 4: Read s_Number
IL_0011:  ret                               // step 5: Return the read value
// }

. LINQPad, IL , (/o + ), , , IL, LINQPad :

void Main() { } // Necessary for LINQPad/Compiler to be happy

private static int s_Number = 0;
public static int GetNumber()
{
    s_Number++;
    return s_Number;
}
+10

, :

s_number = 0

Thread A do s_number ++

s_number = 1

Thread B do s_number ++

s_number = 2

Thread A do return s_number

Thread B do return s_number

2.


, :

class Counter
{
    private static int s_Number = 0;
    private static object _locker = new object();
    public static int GetNextNumber()
    {
        //Critical section
        return Interlocked.Increment(ref s_Number);
    }
}

. , , Lock.

: . , .

+8

, , GetNextNumber, IL code,

class Counter
{
    private static int s_Number = 0;
    public static int GetNextNumber()
    {
        s_Number++;
        return s_Number;
    }
}

IL, , , s_number++ , , , .

Counter.GetNextNumber:
IL_0000:  ldsfld      UserQuery+Counter.s_Number
IL_0005:  ldc.i4.1    
IL_0006:  add         
IL_0007:  stsfld      UserQuery+Counter.s_Number
IL_000C:  ldsfld      UserQuery+Counter.s_Number
IL_0011:  ret         

SCENARIO,

thread A s_Number (IL_0000), 1, thread A thread B. , , , s_number, - 0, B , A. 1. A , , 1 0 B.

concurrency

class CounterLocked
{
 private static object o;
 private static int s_Number = 0;
 public static int GetNextNumber()
 {
  lock(o)
  {
    s_Number++;
    return s_Number;
  }
 }
}

CounterLocked.GetNextNumber:
IL_0000:  ldc.i4.0    
IL_0001:  stloc.0     // <>s__LockTaken0
IL_0002:  ldsfld      UserQuery+CounterLocked.o
IL_0007:  dup         
IL_0008:  stloc.2     // CS$2$0001
IL_0009:  ldloca.s    00 // <>s__LockTaken0
IL_000B:  call        System.Threading.Monitor.Enter
IL_0010:  ldsfld      UserQuery+CounterLocked.s_Number
IL_0015:  ldc.i4.1    
IL_0016:  add         
IL_0017:  stsfld      UserQuery+CounterLocked.s_Number
IL_001C:  ldsfld      UserQuery+CounterLocked.s_Number
IL_0021:  stloc.1     // CS$1$0000
IL_0022:  leave.s     IL_002E
IL_0024:  ldloc.0     // <>s__LockTaken0
IL_0025:  brfalse.s   IL_002D
IL_0027:  ldloc.2     // CS$2$0001
IL_0028:  call        System.Threading.Monitor.Exit
IL_002D:  endfinally  
IL_002E:  ldloc.1     // CS$1$0000
IL_002F:  ret        

, InterlockIncrement,

public static int GetNextNumber()
{
    return Interlocked.Increment(ref s_Number);
}

CounterLocked.GetNextNumber:
IL_0000:  ldsflda     UserQuery+CounterLocked.s_Number
IL_0005:  call        System.Threading.Interlocked.Increment
IL_000A:  ret  
+1

return Interlocked.Increment(ref s_Number);

It will do it. This is much easier than using locks. Blocking blocks should usually be used for code blocks, usually.

0
source

All Articles