05、临界区与原子操作
临界区与原子操作
知识点
临界区(Critical Section)
临界区是指访问共享资源的代码段,在同一时刻只能有一个线程执行。临界区的正确管理是避免竞态条件的关键。
原子操作(Atomic Operation)
原子操作是不可分割的操作,要么完全执行,要么完全不执行,不会被其他线程中断。
.NET中的原子操作支持
- Interlocked类:提供了多种原子操作方法
- volatile关键字:确保变量的读写操作是原子的
- Memory Barriers:控制内存访问的顺序
Interlocked类的主要方法
Increment/Decrement:原子递增/递减Add:原子加法Exchange:原子交换CompareExchange:原子比较并交换
代码案例
案例1:临界区问题演示
using System;
using System.Threading;
using System.Threading.Tasks;
class BankAccount
{
private decimal balance = 1000;
// 不安全的转账方法(没有保护临界区)
public void UnsafeTransfer(decimal amount)
{
// 临界区开始
decimal currentBalance = balance;
Thread.Sleep(1); // 模拟一些处理时间
if (currentBalance >= amount)
{
balance = currentBalance - amount;
Console.WriteLine($"转账 {amount:C},余额: {balance:C}");
}
else
{
Console.WriteLine($"余额不足,当前余额: {balance:C}");
}
// 临界区结束
}
public decimal GetBalance() => balance;
}
class Program
{
static void Main(string[] args)
{
var account = new BankAccount();
Console.WriteLine("=== 不安全的并发转账演示 ===");
Console.WriteLine($"初始余额: {account.GetBalance():C}");
// 启动多个并发转账
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() => account.UnsafeTransfer(300));
}
Task.WaitAll(tasks);
Console.WriteLine($"最终余额: {account.GetBalance():C}");
Console.WriteLine("注意:可能出现负余额(竞态条件)");
}
}
案例2:使用lock保护临界区
using System;
using System.Threading;
using System.Threading.Tasks;
class SafeBankAccount
{
private decimal balance = 1000;
private readonly object lockObject = new object();
// 安全的转账方法(使用lock保护临界区)
public void SafeTransfer(decimal amount)
{
lock (lockObject) // 保护临界区
{
if (balance >= amount)
{
Thread.Sleep(1); // 模拟处理时间
balance -= amount;
Console.WriteLine($"转账 {amount:C},余额: {balance:C}");
}
else
{
Console.WriteLine($"余额不足,当前余额: {balance:C}");
}
}
}
public decimal GetBalance()
{
lock (lockObject)
{
return balance;
}
}
}
class Program
{
static void Main(string[] args)
{
var account = new SafeBankAccount();
Console.WriteLine("=== 安全的并发转账演示 ===");
Console.WriteLine($"初始余额: {account.GetBalance():C}");
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() => account.SafeTransfer(300));
}
Task.WaitAll(tasks);
Console.WriteLine($"最终余额: {account.GetBalance():C}");
Console.WriteLine("余额始终保持非负值");
}
}
案例3:Interlocked原子操作
using System;
using System.Threading;
using System.Threading.Tasks;
class AtomicOperationsDemo
{
private static int counter = 0;
private static long longCounter = 0;
static void Main(string[] args)
{
Console.WriteLine("=== Interlocked原子操作演示 ===");
// 演示原子递增
DemonstrateAtomicIncrement();
// 演示原子交换
DemonstrateAtomicExchange();
// 演示原子比较交换
DemonstrateCompareExchange();
// 演示原子加法
DemonstrateAtomicAdd();
}
static void DemonstrateAtomicIncrement()
{
Console.WriteLine("\n--- 原子递增演示 ---");
counter = 0;
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
Interlocked.Increment(ref counter);
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"原子递增结果: {counter} (期望: 10000)");
}
static void DemonstrateAtomicExchange()
{
Console.WriteLine("\n--- 原子交换演示 ---");
int value = 100;
Console.WriteLine($"交换前: {value}");
int oldValue = Interlocked.Exchange(ref value, 200);
Console.WriteLine($"交换后: {value}, 原值: {oldValue}");
}
static void DemonstrateCompareExchange()
{
Console.WriteLine("\n--- 原子比较交换演示 ---");
int value = 100;
Console.WriteLine($"初始值: {value}");
// 比较成功的情况
int result1 = Interlocked.CompareExchange(ref value, 200, 100);
Console.WriteLine($"比较100并替换为200: 当前值={value}, 原值={result1}");
// 比较失败的情况
int result2 = Interlocked.CompareExchange(ref value, 300, 100);
Console.WriteLine($"比较100并替换为300: 当前值={value}, 原值={result2}");
}
static void DemonstrateAtomicAdd()
{
Console.WriteLine("\n--- 原子加法演示 ---");
longCounter = 0;
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
int threadId = i;
tasks[i] = Task.Run(() =>
{
long increment = (threadId + 1) * 100;
long oldValue = Interlocked.Add(ref longCounter, increment);
Console.WriteLine($"线程{threadId}: 添加{increment}, 原值={oldValue}, 新值={longCounter}");
});
}
Task.WaitAll(tasks);
Console.WriteLine($"最终结果: {longCounter}");
}
}
案例4:volatile关键字的使用
using System;
using System.Threading;
using System.Threading.Tasks;
class VolatileDemo
{
// 不使用volatile的变量
private static bool flag1 = false;
// 使用volatile的变量
private static volatile bool flag2 = false;
static void Main(string[] args)
{
Console.WriteLine("=== volatile关键字演示 ===");
// 启动读取线程
Task readerTask = Task.Run(() =>
{
int count = 0;
// 等待flag1变为true(可能永远等待)
while (!flag1)
{
count++;
if (count % 1000000 == 0)
{
Console.WriteLine("仍在等待flag1变为true...");
}
}
Console.WriteLine("flag1变为true了!");
// 等待flag2变为true(会及时响应)
while (!flag2)
{
// 忙等待
}
Console.WriteLine("flag2变为true了!");
});
// 启动修改线程
Task writerTask = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("设置flag1为true");
flag1 = true;
Thread.Sleep(1000);
Console.WriteLine("设置flag2为true");
flag2 = true;
});
Task.WaitAll(readerTask, writerTask);
Console.WriteLine("演示完成");
}
}
案例5:自定义原子操作包装类
using System;
using System.Threading;
// 原子布尔值包装类
public class AtomicBoolean
{
private int value;
public AtomicBoolean(bool initialValue = false)
{
value = initialValue ? 1 : 0;
}
public bool Value
{
get => Interlocked.CompareExchange(ref value, 0, 0) == 1;
set => Interlocked.Exchange(ref this.value, value ? 1 : 0);
}
public bool CompareAndSet(bool expected, bool newValue)
{
int expectedInt = expected ? 1 : 0;
int newInt = newValue ? 1 : 0;
return Interlocked.CompareExchange(ref value, newInt, expectedInt) == expectedInt;
}
}
// 原子引用包装类
public class AtomicReference<T> where T : class
{
private T value;
public AtomicReference(T initialValue = null)
{
value = initialValue;
}
public T Value
{
get => Interlocked.CompareExchange(ref value, null, null);
set => Interlocked.Exchange(ref this.value, value);
}
public bool CompareAndSet(T expected, T newValue)
{
return ReferenceEquals(Interlocked.CompareExchange(ref value, newValue, expected), expected);
}
}
class AtomicWrapperDemo
{
static void Main(string[] args)
{
Console.WriteLine("=== 原子包装类演示 ===");
// 原子布尔值演示
var atomicBool = new AtomicBoolean(false);
Console.WriteLine($"初始布尔值: {atomicBool.Value}");
bool changed = atomicBool.CompareAndSet(false, true);
Console.WriteLine($"CAS操作成功: {changed}, 当前值: {atomicBool.Value}");
// 原子引用演示
var atomicRef = new AtomicReference<string>("初始值");
Console.WriteLine($"初始引用值: {atomicRef.Value}");
bool refChanged = atomicRef.CompareAndSet("初始值", "新值");
Console.WriteLine($"引用CAS操作成功: {refChanged}, 当前值: {atomicRef.Value}");
}
}
知识点总结
-
临界区管理:
- 临界区是访问共享资源的代码段
- 必须确保同一时刻只有一个线程能执行临界区代码
- 使用lock、Monitor等同步原语保护临界区
-
原子操作优势:
- 无锁编程,性能更好
- 避免死锁问题
- 提供硬件级别的原子性保证
-
Interlocked类功能:
- 提供多种原子操作方法
- 支持32位和64位整数操作
- 支持引用类型的原子交换
-
volatile关键字:
- 确保变量读写的可见性
- 防止编译器优化导致的问题
- 不能保证复合操作的原子性
-
选择原则:
- 简单数值操作:优先使用Interlocked
- 复杂逻辑:使用lock保护临界区
- 布尔标志:考虑使用volatile
- 性能敏感场景:优先考虑无锁方案
-
注意事项:
- 原子操作只能保证单个操作的原子性
- 复合操作仍需要额外的同步机制
- 过度使用原子操作可能影响可读性

浙公网安备 33010602011771号