C# > Asynchronous Programming > Parallel Programming > Thread Safety and Synchronization
Using Interlocked for Atomic Operations
This snippet demonstrates the use of the Interlocked
class in C# for performing atomic operations on shared variables, ensuring thread safety without explicit locking mechanisms for simple operations like incrementing a counter.
Code Example
This example uses the Interlocked.Increment
method to atomically increment the _count
variable. The Interlocked
class provides atomic operations that are guaranteed to be thread-safe without the need for explicit locks. This is particularly useful for simple operations where the overhead of a full lock is not necessary.
using System;
using System.Threading;
using System.Threading.Tasks;
public class AtomicCounter
{
private int _count = 0;
public void Increment()
{
Interlocked.Increment(ref _count);
}
public int GetCount()
{
return _count;
}
}
public class Example
{
public static void Main(string[] args)
{
AtomicCounter counter = new AtomicCounter();
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
counter.Increment();
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"Final Count: {counter.GetCount()}");
}
}
Concepts Behind the Snippet
The Interlocked
class provides a set of static methods that perform simple atomic operations, such as incrementing, decrementing, adding, or exchanging values. These operations are implemented directly by the CPU and are guaranteed to be atomic, meaning they complete without interruption from other threads. This avoids the overhead and complexity of explicit locking in many scenarios.
Real-Life Use Case
A common use case is in performance counters where you need to track the number of requests processed by a service. Interlocked.Increment
can be used to update the counter in a thread-safe manner without the overhead of a lock. Another example is generating unique IDs in a multi-threaded application, where Interlocked.Increment
can be used to atomically generate the next ID.
Best Practices
Interlocked
class is best suited for simple operations like incrementing, decrementing, or comparing and exchanging values.Interlocked
class only provides atomic operations for specific data types (e.g., int
, long
, float
, double
, and object references).
Interview Tip
Be prepared to discuss the difference between using lock
and Interlocked
. Understand when each is appropriate and the performance trade-offs involved. Be able to explain how Interlocked
operations are implemented at the hardware level using CPU instructions like Compare-and-Swap (CAS).
When to Use Interlocked
Use Interlocked
when you need to perform simple atomic operations on shared variables and want to avoid the overhead of explicit locking. It's particularly useful when performance is critical and the operations are simple enough to be implemented atomically.
Memory Footprint
The memory footprint of using Interlocked
is minimal, as it only involves the shared variable itself. There is no additional memory overhead associated with the Interlocked
class.
Alternatives
If you need more complex synchronization mechanisms, consider using lock
, Mutex
, Semaphore
, or thread-safe collections like ConcurrentQueue
or ConcurrentDictionary
.
Pros
Interlocked
operations are typically faster than using locks due to the atomic nature of the operations.Interlocked
class provides a straightforward API for atomic operations.
Cons
FAQ
-
What types of operations are supported by the Interlocked class?
TheInterlocked
class supports operations such as incrementing, decrementing, adding, exchanging, and comparing and exchanging values. These operations are available for integer, long, single-precision floating point, double-precision floating point, and object reference types. -
Is Interlocked always faster than using a lock?
Yes, for the specific atomic operations it provides. However, using a lock might be necessary for more complex operations involving multiple steps or data access.