C# ReaderWriterLockSlim 类 使用详解

总目录


前言

ReaderWriterLockSlim 是 C# 中用于实现读写锁的高效线程同步工具,位于 System.Threading 命名空间下。它提供了一种轻量级的机制来实现读写锁(Read-Write Lock),允许多个线程同时读取共享资源,但在写入时独占访问。适用于读多写少的场景(如缓存、配置管理)。相较于旧版 ReaderWriterLock,它在性能和设计上更优。


一、核心概念

  • 读写锁机制
    • 读模式(Read Lock):允许多个线程同时读取资源,提高并发性能。
    • 写模式(Write Lock)
      • 独占资源,阻塞所有读/写操作。
      • 当有线程请求写入时,阻止其他线程读取或写入,确保数据一致性。
    • 可升级读模式(Upgradeable Read Lock)
      • 允许线程在持有读锁的情况下升级为写锁。
      • 支持从读锁升级为写锁,并且可以从写锁降级为读锁。
  • 适用场景
    • 高并发读低并发写:高频读取、低频写入的共享资源(如缓存、配置)。
    • 避免阻塞读操作:允许多个线程同时读取共享资源,而不会相互阻塞。
    • 需要细粒度控制读写操作的并发性。

二、基本用法

1. 初始化锁

var rwLock = new ReaderWriterLockSlim();

2. 主要方法和属性

方法作用
EnterReadLock() /ExitReadLock()进入读模式(允许多线程并行读取)/ 释放读锁。
EnterWriteLock() / ExitWriteLock()进入写模式(独占资源)/释放写锁。
EnterUpgradeableReadLock()进入可升级读模式(允许后续升级为写锁)。
ExitUpgradeableReadLock()释放可升级读锁。
CurrentReadCount获取当前持有读锁的线程数。
IsReadLockHeld检查当前线程是否持有读锁。
IsWriteLockHeld检查当前线程是否持有写锁。
IsUpgradeableReadLockHeld检查当前线程是否持有可升级读锁。
Dispose()释放锁资源(继承自 IDisposable)。

三、示例

示例 1:基础读写操作

using System.Threading;

class Program
{
    static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    static Dictionary<int, string> cache = new Dictionary<int, string>();

    static void Main()
    {
        // 启动多个读线程和写线程
        new Thread(WriteData).Start(1);
        new Thread(WriteData).Start(2);
        new Thread(ReadData).Start(1);
        new Thread(ReadData).Start(2);
        new Thread(ReadData).Start(3);
        Console.ReadKey();
    }

    static void ReadData(object key)
    {
        rwLock.EnterReadLock();
        try
        {
            if (cache.TryGetValue((int)key, out var value))
            {
                Console.WriteLine($"读取键 {key}: {value}");
            }
            else
            {
                Console.WriteLine($"读取键 {key} 失败");
            }
        }
        finally
        {
            rwLock.ExitReadLock();
        }
    }

    static void WriteData(object key)
    {
        rwLock.EnterWriteLock();
        try
        {
            cache[(int)key] = $"New Value {new Random().Next()}";
            Console.WriteLine($"写入键 {key}{cache[(int)key]}");
        }
        finally
        {
            rwLock.ExitWriteLock();
        }
    }
}

输出

写入键 1:New Value 1748854959
写入键 2:New Value 1022241692
读取键 1: New Value 1748854959
读取键 2: New Value 1022241692
读取键 3 失败

示例 2:基础读写操作

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    static string sharedResource = "Initial Value";

    static async Task Main(string[] args)
    {
        // 启动多个读线程
        for (int i = 0; i < 5; i++)
        {
            int j = i + 1;
            Task.Run(() => ReadTask($"Reader {j}"));
        }

        // 启动一个写线程
        Task.Run(() => WriteTask("Writer"));

        // 等待所有任务完成
        await Task.Delay(5000);
        Console.WriteLine("所有任务已完成");
    }

    static void ReadTask(string name)
    {
        while (true)
        {
            try
            {
                rwLock.EnterReadLock();
                Console.WriteLine($"{name} 正在读取: {sharedResource}");
                Thread.Sleep(100); // 模拟读取时间
            }
            finally
            {
                rwLock.ExitReadLock();
            }

            // 随机决定是否继续读取
            if (new Random().NextDouble() > 0.8)
            {
                break;
            }
        }
    }

    static void WriteTask(string name)
    {
        while (true)
        {
            try
            {
                rwLock.EnterWriteLock();
                Console.WriteLine($"{name} 正在写入...");
                sharedResource = $"New Value at {DateTime.Now}";
                Console.WriteLine($"{name} 写入完成: {sharedResource}");
                Thread.Sleep(2000); // 模拟写入时间
            }
            finally
            {
                rwLock.ExitWriteLock();
            }

            // 随机决定是否继续写入
            if (new Random().NextDouble() > 0.8)
            {
                break;
            }
        }
    }
}
Reader 2 正在读取: Initial Value
Reader 1 正在读取: Initial Value
Reader 3 正在读取: Initial Value
Reader 4 正在读取: Initial Value
Reader 5 正在读取: Initial Value
Writer 正在写入...
Writer 写入完成: New Value at 2025/2/13 11:23:10
Reader 2 正在读取: New Value at 2025/2/13 11:23:10
Reader 5 正在读取: New Value at 2025/2/13 11:23:10
Reader 3 正在读取: New Value at 2025/2/13 11:23:10
Writer 正在写入...
Writer 写入完成: New Value at 2025/2/13 11:23:12
Reader 3 正在读取: New Value at 2025/2/13 11:23:12
Reader 1 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 3 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
所有任务已完成

四、可升级读锁(Upgradeable Read Lock)

适用场景

  • 需要先读取数据,再根据条件决定是否修改(例如缓存填充)。
  • 避免直接使用写锁导致不必要的阻塞。

有时我们需要先读取资源,然后根据读取的结果决定是否需要进行写操作。这时可以使用可升级读锁(Upgradeable Read Lock)。以下是使用可升级读锁的示例:

示例

static void SafeWriteWithUpgrade(int key)
{
    rwLock.EnterUpgradeableReadLock();
    try
    {
        if (!cache.ContainsKey(key))
        {
            rwLock.EnterWriteLock();
            try
            {
                cache[key] = "Initialized Value";
                Console.WriteLine($"初始化键 {key}");
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
    }
    finally
    {
        rwLock.ExitUpgradeableReadLock();
    }
}
static void UpgradeableReadTask(string name)
{
    while (true)
    {
        try
        {
            rwLock.EnterUpgradeableReadLock();
            Console.WriteLine($"{name} 正在读取: {sharedResource}");

            // 根据某些条件决定是否需要写入
            if (new Random().NextDouble() > 0.8)
            {
                try
                {
                    rwLock.EnterWriteLock();
                    Console.WriteLine($"{name} 正在升级到写锁并写入...");
                    sharedResource = $"Upgraded Value at {DateTime.Now}";
                    Console.WriteLine($"{name} 写入完成: {sharedResource}");
                    Thread.Sleep(2000); // 模拟写入时间
                }
                finally
                {
                    rwLock.ExitWriteLock();
                }
            }

            Thread.Sleep(100); // 模拟读取时间
        }
        finally
        {
            rwLock.ExitUpgradeableReadLock();
        }

        // 随机决定是否继续读取
        if (new Random().NextDouble() > 0.8)
        {
            break;
        }
    }
}

五、注意事项

1. 锁的释放

  • 必须使用 try-finally 确保锁被释放,否则会导致死锁。
  • 在实际应用中,建议在 EnterReadLock()、EnterWriteLock() 和 EnterUpgradeableReadLock() 调用周围添加 try-finally 块,以确保即使在发生异常的情况下也能正确地释放锁。
  • 错误示例:
    rwLock.EnterReadLock();
    // 若此处抛出异常,锁未释放!
    rwLock.ExitReadLock();
    

2. 递归调用

  • 默认情况下,ReaderWriterLockSlim 不支持递归调用
  • 若需递归,需使用 LockRecursionPolicy.SupportsRecursion 初始化:
    var rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    

3. 性能优化

  • 避免长时间持有写锁,以减少阻塞。
  • 可升级读锁仅在必要时使用,因其性能开销较高。

六、高级用法

1. 超时等待

bool acquired = rwLock.TryEnterWriteLock(TimeSpan.FromSeconds(2));
if (acquired)
{
    try
    {
        // 执行写入操作
    }
    finally
    {
        rwLock.ExitWriteLock();
    }
}
else
{
    Console.WriteLine("获取写锁超时");
}

2. 结合异步编程

async Task UpdateCacheAsync(int key)
{
    await Task.Run(() =>
    {
        rwLock.EnterWriteLock();
        try
        {
            cache[key] = await FetchDataFromNetworkAsync();
        }
        finally
        {
            rwLock.ExitWriteLock();
        }
    });
}

七、替代方案

  • ConcurrentDictionary:内置线程安全的字典,适合简单读写场景。
  • Immutable Collections:不可变集合,天然线程安全。
  • MonitorMutex:适合简单独占访问,但无法区分读写。

总结

ReaderWriterLockSlim 是实现高效读写分离同步的核心工具,尤其适用于读多写少的场景。通过合理使用读锁、写锁和可升级读锁,可以显著提升并发性能。但需注意锁的释放、递归策略及死锁风险。在高并发场景中,建议结合性能测试选择最优方案。


结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
xxx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲤籽鲲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值