编辑
2025-09-20
C#
00

目录

什么是Interlocked?
为什么选择Interlocked?
Interlocked的主要方法
实际应用场景
线程安全计数器
使用Interlocked.Add进行原子累加
实现线程安全的标志位(布尔值)
使用Exchange进行原子交换
使用CompareExchange实现复杂的条件更新
性能对比:Interlocked vs. lock
Interlocked的局限性
何时使用Interlocked?
总结

在多线程应用程序开发中,保证共享变量的线程安全是一个常见的挑战。.NET提供了一个强大而轻量级的解决方案——System.Threading.Interlocked类。它能够执行原子操作,无需使用传统锁机制,就能有效避免竞态条件。本文将详细介绍Interlocked的使用方法和实际应用场景。

什么是Interlocked?

Interlocked是.NET框架中System.Threading命名空间下的一个静态类,专门用于提供线程安全的原子操作。在多线程环境中,当多个线程同时访问和修改共享变量时,如果不采取同步措施,就会导致数据不一致,这就是所谓的"竞态条件"。

Interlocked通过底层的CPU原子指令来保证操作的原子性,确保一个操作完成不会被其他线程中断。

为什么选择Interlocked?

相比于传统的锁机制(如lock关键字或Monitor类),Interlocked具有以下优势:

  1. 原子性操作:操作保证不会被中断,避免了竞态条件
  2. 轻量级:性能开销远小于锁机制
  3. 无锁设计:减少了死锁的可能性
  4. 简洁易用:API设计简单直观

Interlocked的主要方法

Interlocked类提供了多种方法来支持各种原子操作:

  • Increment(ref int location):原子地将变量加1
  • Decrement(ref int location):原子地将变量减1
  • Add(ref int location, int value):原子地将值添加到变量
  • Exchange(ref T location, T value):原子地替换变量的值
  • CompareExchange(ref T location, T value, T comparand):比较并交换值

实际应用场景

线程安全计数器

计数器是Interlocked最常见的应用场景之一。下面是一个使用10个线程同时递增计数器的例子:

C#
namespace AppInterlocked { internal class Program { // 共享计数器变量 private static int counter = 0; static void Main() { // 创建10个线程 Thread[] threads = new Thread[10]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(() => { // 每个线程递增计数器1000次 for (int j = 0; j < 1000; j++) { Interlocked.Increment(ref counter); // 如果使用 counter++ 则可能导致计数不准确 } }); threads[i].Start(); } // 等待所有线程完成 foreach (var t in threads) t.Join(); Console.WriteLine($"最终计数器值: {counter}"); } } }

image.png

使用Interlocked.Add进行原子累加

除了递增和递减外,Interlocked.Add方法允许我们原子地将任意值添加到变量:

C#
namespace AppInterlocked { internal class Program { private static int total = 0; static void Main() { // 使用并行任务 Parallel.For(0, 100, i => { // 每个任务添加不同的值 Interlocked.Add(ref total, i); }); Console.WriteLine($"累加结果: {total}"); } } }

image.png

实现线程安全的标志位(布尔值)

虽然Interlocked不直接支持布尔类型,但我们可以使用整数来模拟布尔标志位:

C#
namespace AppInterlocked { internal class Program { private static int total = 0; // 使用0表示false,1表示true private static int isRunning = 0; static void StartProcess() { // 尝试将isRunning从0设为1,如果成功(返回值是0)则执行 if (Interlocked.CompareExchange(ref isRunning, 1, 0) == 0) { try { Console.WriteLine("进程开始执行..."); // 执行需要互斥的操作 Task.Delay(1000).Wait(); // 模拟工作 Console.WriteLine("进程执行完成"); } finally { // 完成后重置标志位 Interlocked.Exchange(ref isRunning, 0); } } else { Console.WriteLine("进程已在运行中,无法启动"); } } static void Main() { Task[] tasks = new Task[5]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => StartProcess()); } Task.WaitAll(tasks); } } }

image.png

使用Exchange进行原子交换

Interlocked.Exchange方法可以原子地交换变量的值:

C#
namespace AppInterlocked { internal class Program { private static string sharedResource = "初始值"; static void UpdateResource(string newValue) { // 原子地用新值替换旧值,并返回旧值 string oldValue = Interlocked.Exchange(ref sharedResource, newValue); Console.WriteLine($"资源从 '{oldValue}' 更新为 '{newValue}'"); } static void Main() { Thread t1 = new Thread(() => UpdateResource("线程1的值")); Thread t2 = new Thread(() => UpdateResource("线程2的值")); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine($"最终资源值: {sharedResource}"); } } }

image.png

使用CompareExchange实现复杂的条件更新

CompareExchange是最强大的Interlocked方法,它允许我们在满足特定条件时才进行更新:

C#
namespace AppInterlocked { internal class Program { private static int counter = 0; static void ConditionalIncrement(int threshold) { int current, newValue; do { // 读取当前值 current = counter; // 如果当前值已经达到或超过阈值,则不更新 if (current >= threshold) { Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}: 值 {current} 已达到阈值 {threshold},不再增加"); return; } // 计算新值 newValue = current + 1; } while (Interlocked.CompareExchange(ref counter, newValue, current) != current); Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId}: 将计数器从 {current} 增加到 {newValue}"); } static void Main() { const int threshold = 10; Task[] tasks = new Task[20]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => ConditionalIncrement(threshold)); } Task.WaitAll(tasks); Console.WriteLine($"最终计数器值: {counter}"); } } }

image.png

性能对比:Interlocked vs. lock

为了理解Interlocked的性能优势,我们来看一个简单的对比:

C#
using System.Diagnostics; namespace AppInterlocked { internal class Program { private static int counterInterlocked = 0; private static int counterLock = 0; private static object lockObj = new object(); static void Main() { const int operationsCount = 10000000; const int threadsCount = 8; // 测试Interlocked Stopwatch swInterlocked = Stopwatch.StartNew(); Parallel.For(0, threadsCount, _ => { for (int i = 0; i < operationsCount / threadsCount; i++) { Interlocked.Increment(ref counterInterlocked); } }); swInterlocked.Stop(); // 测试lock Stopwatch swLock = Stopwatch.StartNew(); Parallel.For(0, threadsCount, _ => { for (int i = 0; i < operationsCount / threadsCount; i++) { lock (lockObj) { counterLock++; } } }); swLock.Stop(); Console.WriteLine($"Interlocked 计数器: {counterInterlocked}, 耗时: {swInterlocked.ElapsedMilliseconds}ms"); Console.WriteLine($"Lock 计数器: {counterLock}, 耗时: {swLock.ElapsedMilliseconds}ms"); Console.WriteLine($"性能差异比: {(double)swLock.ElapsedMilliseconds / swInterlocked.ElapsedMilliseconds:F2}x"); } } }

image.png

Interlocked的局限性

虽然Interlocked功能强大,但它也有一些局限性:

  1. 仅支持简单的原子操作:不适合复杂的数据结构或多个变量的原子更新
  2. 有限的数据类型支持:主要支持整数类型、引用类型和指针
  3. 不支持布尔类型:需要使用整数来模拟
  4. 不适合长时间运行的操作:只适合快速、简单的原子操作

何时使用Interlocked?

Interlocked最适合以下场景:

  • 简单计数器:需要线程安全递增或递减
  • 标志位:需要原子地设置或清除状态标志
  • 简单共享变量:需要原子更新
  • 性能关键型应用:需要高性能的线程同步
  • 避免死锁:需要无锁设计的场景

对于更复杂的场景,考虑使用:

  • lock语句:需要保护代码块
  • ReaderWriterLockSlim:读多写少的场景
  • 线程安全集合:如ConcurrentDictionaryConcurrentQueue

总结

Interlocked类是.NET多线程编程中的重要工具,它提供了轻量级、高性能的原子操作,非常适合简单共享变量的线程安全更新。通过避免传统的锁机制,它可以显著提高多线程应用程序的性能,减少线程同步开销。

虽然有一些局限性,但在适当的场景中使用Interlocked可以让我们的多线程代码更加简洁、高效且可靠。无论是实现计数器、标志位还是其他简单的共享状态,Interlocked都是我们在.NET多线程编程中的强大盟友。

作为C#开发者,掌握Interlocked的使用将让你的多线程代码更加高效,也是迈向高级多线程编程的重要一步。


关键词:C#线程安全操作、Interlocked使用教程、.NET多线程编程、无锁线程同步、原子操作、线程安全计数器、CompareExchange实例、多线程性能优化

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!