One of the most common mistakes in multithreaded programming is assuming that operations on in-memory variables are automatically atomic.

They are not.

Even a simple statement like  counter++; is actually composed of multiple steps:

  1. Read the current value
  2. Increment the value
  3. Write the new value back

If multiple threads execute this simultaneously, race conditions can occur and updates may be lost.


For atomic integer operations

1- Interlocke (Lightweight atomic operations on numeric operations)

Interlocked provides lightweight atomic operations without requiring explicit locks, making it ideal for counters, statistics, and shared numeric state.

    private int _value;

    Interlocked.Increment(ref _value);
    Interlocked.Decrement(ref _value);
    Interlocked.Add(ref _value, 5);

//Reading safely:
    int current = Volatile.Read(ref _value);

This is the standard solution for thread-safe integer mutation.

 

2- ReaderWriterLockSlim. For many readers / single writer semantics

useful when:

  • many threads read data frequently
  • writes are relatively rare
  • multiple operations must be protected together
    private readonly ReaderWriterLockSlim _lock = new();
    private int _value;
    
    public int Read()
    {
        _lock.EnterReadLock();
        try
        {
            return _value;
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }
    
    public void Increment()
    {
        _lock.EnterWriteLock();
        try
        {
            _value++;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

This gives:

  • multiple concurrent readers
  • only one writer at a time

which is useful for read-heavy workloads.

 

 

Creating a Reusable Concurrent Integer

You can also encapsulate atomic operations in a reusable type:

public class ConcurrentInt
{
    private int _value;

    public ConcurrentInt(int initialValue = 0)
   {
         _value = initialValue;
   }
    public int Value  =>  Volatile.Read(ref _value);
    public int Increment()  =>  Interlocked.Increment(ref _value);
    public int Decrement()  =>  Interlocked.Decrement(ref _value);
    public int Add(int amount)  =>  Interlocked.Add(ref _value, amount);
}

Usage:

var x = new ConcurrentInt(14);
x.Increment();
Console.WriteLine(x.Value);

 

Summery

  • ++ and -- are not atomic
  • race conditions can occur under concurrency
  • Interlocked is the preferred solution for atomic primitive updates
  • ReaderWriterLockSlim is better for protecting larger shared state
  • never assume in-memory operations are thread-safe by default
/