05、临界区与原子操作

临界区与原子操作

知识点

临界区(Critical Section)

临界区是指访问共享资源的代码段,在同一时刻只能有一个线程执行。临界区的正确管理是避免竞态条件的关键。

原子操作(Atomic Operation)

原子操作是不可分割的操作,要么完全执行,要么完全不执行,不会被其他线程中断。

.NET中的原子操作支持

  1. Interlocked类:提供了多种原子操作方法
  2. volatile关键字:确保变量的读写操作是原子的
  3. 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}");
    }
}

知识点总结

  1. 临界区管理:

    • 临界区是访问共享资源的代码段
    • 必须确保同一时刻只有一个线程能执行临界区代码
    • 使用lock、Monitor等同步原语保护临界区
  2. 原子操作优势:

    • 无锁编程,性能更好
    • 避免死锁问题
    • 提供硬件级别的原子性保证
  3. Interlocked类功能:

    • 提供多种原子操作方法
    • 支持32位和64位整数操作
    • 支持引用类型的原子交换
  4. volatile关键字:

    • 确保变量读写的可见性
    • 防止编译器优化导致的问题
    • 不能保证复合操作的原子性
  5. 选择原则:

    • 简单数值操作:优先使用Interlocked
    • 复杂逻辑:使用lock保护临界区
    • 布尔标志:考虑使用volatile
    • 性能敏感场景:优先考虑无锁方案
  6. 注意事项:

    • 原子操作只能保证单个操作的原子性
    • 复合操作仍需要额外的同步机制
    • 过度使用原子操作可能影响可读性
posted @ 2025-08-27 01:24  jessqiu  阅读(52)  评论(0)    收藏  举报