09、读写锁

读写锁

知识点

什么是读写锁

读写锁(Reader-Writer Lock)是一种特殊的同步机制,允许多个线程同时读取共享资源,但写入时需要独占访问。这种机制在读多写少的场景下能显著提高性能。

ReaderWriterLockSlim类

.NET提供了ReaderWriterLockSlim类来实现读写锁功能,它是ReaderWriterLock的改进版本,性能更好,功能更强。

读写锁的工作原理

  • 读锁(共享锁):多个线程可以同时持有读锁
  • 写锁(独占锁):只有一个线程可以持有写锁
  • 升级锁:从读锁升级为写锁的特殊机制

锁的类型

  1. 读锁(Read Lock):允许并发读取
  2. 写锁(Write Lock):独占写入
  3. 可升级读锁(Upgradeable Read Lock):可以升级为写锁的读锁

使用场景

  • 缓存系统
  • 配置管理
  • 数据字典
  • 读多写少的数据结构

代码案例

案例1:基本的读写锁使用

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class BasicReaderWriterLock
{
    private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static Dictionary<string, string> cache = new Dictionary<string, string>();
    
    static void Main(string[] args)
    {
        Console.WriteLine("基本读写锁示例");
        
        // 初始化一些数据
        InitializeCache();
        
        // 启动多个读线程
        Task[] readers = new Task[5];
        for (int i = 0; i < readers.Length; i++)
        {
            int readerId = i;
            readers[i] = Task.Run(() => ReaderTask(readerId));
        }
        
        // 启动少量写线程
        Task[] writers = new Task[2];
        for (int i = 0; i < writers.Length; i++)
        {
            int writerId = i;
            writers[i] = Task.Run(() => WriterTask(writerId));
        }
        
        // 等待所有任务完成
        Task.WaitAll(readers.Concat(writers).ToArray());
        
        Console.WriteLine("所有任务完成");
        DisplayCache();
        
        // 清理资源
        rwLock.Dispose();
    }
    
    static void InitializeCache()
    {
        rwLock.EnterWriteLock();
        try
        {
            cache["key1"] = "value1";
            cache["key2"] = "value2";
            cache["key3"] = "value3";
            Console.WriteLine("缓存初始化完成");
        }
        finally
        {
            rwLock.ExitWriteLock();
        }
    }
    
    static void ReaderTask(int readerId)
    {
        for (int i = 0; i < 10; i++)
        {
            rwLock.EnterReadLock();
            try
            {
                Console.WriteLine($"读线程{readerId}: 开始读取缓存");
                
                // 模拟读取操作
                foreach (var kvp in cache)
                {
                    Console.WriteLine($"读线程{readerId}: {kvp.Key} = {kvp.Value}");
                }
                
                Thread.Sleep(100); // 模拟读取时间
                Console.WriteLine($"读线程{readerId}: 读取完成");
            }
            finally
            {
                rwLock.ExitReadLock();
            }
            
            Thread.Sleep(200); // 等待一段时间再次读取
        }
    }
    
    static void WriterTask(int writerId)
    {
        for (int i = 0; i < 5; i++)
        {
            rwLock.EnterWriteLock();
            try
            {
                Console.WriteLine($"写线程{writerId}: 开始写入缓存");
                
                string key = $"writer{writerId}_key{i}";
                string value = $"writer{writerId}_value{i}_{DateTime.Now:HH:mm:ss.fff}";
                
                cache[key] = value;
                Console.WriteLine($"写线程{writerId}: 写入 {key} = {value}");
                
                Thread.Sleep(500); // 模拟写入时间
                Console.WriteLine($"写线程{writerId}: 写入完成");
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
            
            Thread.Sleep(1000); // 等待一段时间再次写入
        }
    }
    
    static void DisplayCache()
    {
        rwLock.EnterReadLock();
        try
        {
            Console.WriteLine("\n=== 最终缓存内容 ===");
            foreach (var kvp in cache)
            {
                Console.WriteLine($"{kvp.Key} = {kvp.Value}");
            }
        }
        finally
        {
            rwLock.ExitReadLock();
        }
    }
}

案例2:可升级读锁的使用

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class UpgradeableReadLockExample
{
    private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static Dictionary<string, CacheItem> cache = new Dictionary<string, CacheItem>();
    
    class CacheItem
    {
        public string Value { get; set; }
        public DateTime LastAccessed { get; set; }
        public int AccessCount { get; set; }
    }
    
    static void Main(string[] args)
    {
        Console.WriteLine("可升级读锁示例");
        
        // 初始化缓存
        InitializeCache();
        
        // 启动多个任务
        Task[] tasks = new Task[8];
        
        // 读任务
        for (int i = 0; i < 4; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(() => ReadWithStatistics(taskId));
        }
        
        // 可升级读任务
        for (int i = 4; i < 6; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(() => UpgradeableReadTask(taskId));
        }
        
        // 纯写任务
        for (int i = 6; i < 8; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(() => WriteTask(taskId));
        }
        
        Task.WaitAll(tasks);
        
        Console.WriteLine("所有任务完成");
        DisplayCacheStatistics();
        
        rwLock.Dispose();
    }
    
    static void InitializeCache()
    {
        rwLock.EnterWriteLock();
        try
        {
            for (int i = 1; i <= 5; i++)
            {
                cache[$"item{i}"] = new CacheItem
                {
                    Value = $"value{i}",
                    LastAccessed = DateTime.Now,
                    AccessCount = 0
                };
            }
            Console.WriteLine("缓存初始化完成");
        }
        finally
        {
            rwLock.ExitWriteLock();
        }
    }
    
    static void ReadWithStatistics(int taskId)
    {
        for (int i = 0; i < 10; i++)
        {
            string key = $"item{(i % 5) + 1}";
            
            // 使用可升级读锁来读取并更新统计信息
            rwLock.EnterUpgradeableReadLock();
            try
            {
                Console.WriteLine($"任务{taskId}: 尝试读取 {key}");
                
                if (cache.ContainsKey(key))
                {
                    var item = cache[key];
                    Console.WriteLine($"任务{taskId}: 读取到 {key} = {item.Value}");
                    
                    // 需要更新统计信息,升级为写锁
                    rwLock.EnterWriteLock();
                    try
                    {
                        item.LastAccessed = DateTime.Now;
                        item.AccessCount++;
                        Console.WriteLine($"任务{taskId}: 更新 {key} 的统计信息,访问次数: {item.AccessCount}");
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                    }
                }
                else
                {
                    Console.WriteLine($"任务{taskId}: {key} 不存在");
                }
            }
            finally
            {
                rwLock.ExitUpgradeableReadLock();
            }
            
            Thread.Sleep(300);
        }
    }
    
    static void UpgradeableReadTask(int taskId)
    {
        for (int i = 0; i < 5; i++)
        {
            string key = $"upgrade_item{i}";
            
            rwLock.EnterUpgradeableReadLock();
            try
            {
                Console.WriteLine($"升级任务{taskId}: 检查 {key} 是否存在");
                
                if (!cache.ContainsKey(key))
                {
                    // 如果不存在,升级为写锁并创建
                    rwLock.EnterWriteLock();
                    try
                    {
                        Console.WriteLine($"升级任务{taskId}: {key} 不存在,创建新项");
                        cache[key] = new CacheItem
                        {
                            Value = $"upgraded_value{taskId}_{i}",
                            LastAccessed = DateTime.Now,
                            AccessCount = 1
                        };
                        Console.WriteLine($"升级任务{taskId}: 创建 {key} 完成");
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                    }
                }
                else
                {
                    Console.WriteLine($"升级任务{taskId}: {key} 已存在,值为 {cache[key].Value}");
                }
            }
            finally
            {
                rwLock.ExitUpgradeableReadLock();
            }
            
            Thread.Sleep(800);
        }
    }
    
    static void WriteTask(int taskId)
    {
        for (int i = 0; i < 3; i++)
        {
            rwLock.EnterWriteLock();
            try
            {
                string key = $"write_item{taskId}_{i}";
                Console.WriteLine($"写任务{taskId}: 写入 {key}");
                
                cache[key] = new CacheItem
                {
                    Value = $"write_value{taskId}_{i}_{DateTime.Now:HH:mm:ss.fff}",
                    LastAccessed = DateTime.Now,
                    AccessCount = 0
                };
                
                Thread.Sleep(200); // 模拟写入时间
                Console.WriteLine($"写任务{taskId}: {key} 写入完成");
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
            
            Thread.Sleep(1500);
        }
    }
    
    static void DisplayCacheStatistics()
    {
        rwLock.EnterReadLock();
        try
        {
            Console.WriteLine("\n=== 缓存统计信息 ===");
            foreach (var kvp in cache)
            {
                var item = kvp.Value;
                Console.WriteLine($"{kvp.Key}: 值={item.Value}, 访问次数={item.AccessCount}, 最后访问={item.LastAccessed:HH:mm:ss.fff}");
            }
        }
        finally
        {
            rwLock.ExitReadLock();
        }
    }
}

案例3:超时和异常处理

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class ReaderWriterLockTimeout
{
    private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static Dictionary<string, string> dataStore = new Dictionary<string, string>();
    
    static void Main(string[] args)
    {
        Console.WriteLine("读写锁超时处理示例");
        
        // 启动一个长时间持有写锁的任务
        Task longWriteTask = Task.Run(() => LongWriteOperation());
        Thread.Sleep(100); // 确保写任务先获得锁
        
        // 启动多个尝试获取锁的任务
        Task[] tasks = new Task[6];
        
        for (int i = 0; i < 3; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(() => TryReadWithTimeout(taskId));
        }
        
        for (int i = 3; i < 6; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(() => TryWriteWithTimeout(taskId));
        }
        
        Task.WaitAll(new[] { longWriteTask }.Concat(tasks).ToArray());
        
        Console.WriteLine("所有任务完成");
        rwLock.Dispose();
    }
    
    static void LongWriteOperation()
    {
        Console.WriteLine("长写操作: 开始获取写锁...");
        
        rwLock.EnterWriteLock();
        try
        {
            Console.WriteLine("长写操作: 获得写锁,开始长时间操作...");
            
            for (int i = 0; i < 10; i++)
            {
                dataStore[$"long_key_{i}"] = $"long_value_{i}";
                Thread.Sleep(500); // 模拟长时间写入
                Console.WriteLine($"长写操作: 写入进度 {i + 1}/10");
            }
            
            Console.WriteLine("长写操作: 完成");
        }
        finally
        {
            rwLock.ExitWriteLock();
            Console.WriteLine("长写操作: 释放写锁");
        }
    }
    
    static void TryReadWithTimeout(int taskId)
    {
        for (int attempt = 0; attempt < 3; attempt++)
        {
            Console.WriteLine($"读任务{taskId}: 尝试获取读锁(尝试 {attempt + 1}/3)...");
            
            try
            {
                if (rwLock.TryEnterReadLock(TimeSpan.FromSeconds(2)))
                {
                    try
                    {
                        Console.WriteLine($"读任务{taskId}: 成功获得读锁");
                        
                        // 执行读操作
                        int count = dataStore.Count;
                        Console.WriteLine($"读任务{taskId}: 当前数据项数量: {count}");
                        
                        Thread.Sleep(300); // 模拟读取时间
                    }
                    finally
                    {
                        rwLock.ExitReadLock();
                        Console.WriteLine($"读任务{taskId}: 释放读锁");
                    }
                    
                    break; // 成功后退出重试循环
                }
                else
                {
                    Console.WriteLine($"读任务{taskId}: 获取读锁超时,执行替代方案");
                    
                    // 执行不需要锁的替代操作
                    Console.WriteLine($"读任务{taskId}: 从缓存或其他源获取数据");
                    Thread.Sleep(100);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"读任务{taskId}: 异常 - {ex.Message}");
            }
            
            Thread.Sleep(1000); // 等待后重试
        }
    }
    
    static void TryWriteWithTimeout(int taskId)
    {
        for (int attempt = 0; attempt < 2; attempt++)
        {
            Console.WriteLine($"写任务{taskId}: 尝试获取写锁(尝试 {attempt + 1}/2)...");
            
            try
            {
                if (rwLock.TryEnterWriteLock(TimeSpan.FromSeconds(3)))
                {
                    try
                    {
                        Console.WriteLine($"写任务{taskId}: 成功获得写锁");
                        
                        // 执行写操作
                        string key = $"task{taskId}_attempt{attempt}";
                        string value = $"value_{DateTime.Now:HH:mm:ss.fff}";
                        dataStore[key] = value;
                        
                        Console.WriteLine($"写任务{taskId}: 写入 {key} = {value}");
                        Thread.Sleep(200); // 模拟写入时间
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                        Console.WriteLine($"写任务{taskId}: 释放写锁");
                    }
                    
                    break; // 成功后退出重试循环
                }
                else
                {
                    Console.WriteLine($"写任务{taskId}: 获取写锁超时");
                    
                    // 可以选择将写操作加入队列或记录日志
                    Console.WriteLine($"写任务{taskId}: 将写操作加入待处理队列");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"写任务{taskId}: 异常 - {ex.Message}");
            }
            
            Thread.Sleep(1500); // 等待后重试
        }
    }
}

案例4:读写锁性能对比

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

class ReaderWriterLockPerformance
{
    private static Dictionary<int, string> data = new Dictionary<int, string>();
    private static readonly object simpleLock = new object();
    private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    
    static void Main(string[] args)
    {
        Console.WriteLine("读写锁性能对比示例");
        
        // 初始化数据
        InitializeData();
        
        int readTasks = 10;
        int writeTasks = 2;
        int operationsPerTask = 1000;
        
        Console.WriteLine($"测试参数: {readTasks}个读任务, {writeTasks}个写任务, 每个任务{operationsPerTask}次操作");
        
        // 测试简单锁性能
        TimeSpan simpleLockTime = TestSimpleLock(readTasks, writeTasks, operationsPerTask);
        
        // 重置数据
        InitializeData();
        
        // 测试读写锁性能
        TimeSpan rwLockTime = TestReaderWriterLock(readTasks, writeTasks, operationsPerTask);
        
        // 输出对比结果
        Console.WriteLine("\n=== 性能对比结果 ===");
        Console.WriteLine($"简单锁耗时: {simpleLockTime.TotalMilliseconds:F2} ms");
        Console.WriteLine($"读写锁耗时: {rwLockTime.TotalMilliseconds:F2} ms");
        Console.WriteLine($"性能提升: {(simpleLockTime.TotalMilliseconds / rwLockTime.TotalMilliseconds):F2}x");
        
        rwLock.Dispose();
    }
    
    static void InitializeData()
    {
        data.Clear();
        for (int i = 0; i < 1000; i++)
        {
            data[i] = $"value_{i}";
        }
    }
    
    static TimeSpan TestSimpleLock(int readTasks, int writeTasks, int operationsPerTask)
    {
        Console.WriteLine("\n--- 测试简单锁 ---");
        
        var stopwatch = Stopwatch.StartNew();
        
        Task[] allTasks = new Task[readTasks + writeTasks];
        
        // 创建读任务
        for (int i = 0; i < readTasks; i++)
        {
            int taskId = i;
            allTasks[i] = Task.Run(() => SimpleLockReadTask(taskId, operationsPerTask));
        }
        
        // 创建写任务
        for (int i = 0; i < writeTasks; i++)
        {
            int taskId = i;
            allTasks[readTasks + i] = Task.Run(() => SimpleLockWriteTask(taskId, operationsPerTask));
        }
        
        Task.WaitAll(allTasks);
        stopwatch.Stop();
        
        Console.WriteLine($"简单锁测试完成,耗时: {stopwatch.ElapsedMilliseconds} ms");
        return stopwatch.Elapsed;
    }
    
    static TimeSpan TestReaderWriterLock(int readTasks, int writeTasks, int operationsPerTask)
    {
        Console.WriteLine("\n--- 测试读写锁 ---");
        
        var stopwatch = Stopwatch.StartNew();
        
        Task[] allTasks = new Task[readTasks + writeTasks];
        
        // 创建读任务
        for (int i = 0; i < readTasks; i++)
        {
            int taskId = i;
            allTasks[i] = Task.Run(() => ReaderWriterLockReadTask(taskId, operationsPerTask));
        }
        
        // 创建写任务
        for (int i = 0; i < writeTasks; i++)
        {
            int taskId = i;
            allTasks[readTasks + i] = Task.Run(() => ReaderWriterLockWriteTask(taskId, operationsPerTask));
        }
        
        Task.WaitAll(allTasks);
        stopwatch.Stop();
        
        Console.WriteLine($"读写锁测试完成,耗时: {stopwatch.ElapsedMilliseconds} ms");
        return stopwatch.Elapsed;
    }
    
    static void SimpleLockReadTask(int taskId, int operations)
    {
        for (int i = 0; i < operations; i++)
        {
            lock (simpleLock)
            {
                int key = i % 1000;
                string value = data.ContainsKey(key) ? data[key] : null;
                // 模拟读取处理
            }
        }
    }
    
    static void SimpleLockWriteTask(int taskId, int operations)
    {
        for (int i = 0; i < operations; i++)
        {
            lock (simpleLock)
            {
                int key = (taskId * 10000) + i;
                data[key] = $"task{taskId}_value_{i}";
            }
        }
    }
    
    static void ReaderWriterLockReadTask(int taskId, int operations)
    {
        for (int i = 0; i < operations; i++)
        {
            rwLock.EnterReadLock();
            try
            {
                int key = i % 1000;
                string value = data.ContainsKey(key) ? data[key] : null;
                // 模拟读取处理
            }
            finally
            {
                rwLock.ExitReadLock();
            }
        }
    }
    
    static void ReaderWriterLockWriteTask(int taskId, int operations)
    {
        for (int i = 0; i < operations; i++)
        {
            rwLock.EnterWriteLock();
            try
            {
                int key = (taskId * 10000) + i;
                data[key] = $"task{taskId}_value_{i}";
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
    }
}

知识点总结

  1. 读写锁的优势:

    • 允许多个读者并发访问
    • 写操作时独占访问
    • 在读多写少场景下性能显著提升
  2. 锁的类型和使用:

    • 读锁:EnterReadLock()/ExitReadLock()
    • 写锁:EnterWriteLock()/ExitWriteLock()
    • 可升级读锁:EnterUpgradeableReadLock()/ExitUpgradeableReadLock()
  3. 最佳实践:

    • 总是使用try-finally确保锁的释放
    • 使用TryEnter方法设置超时
    • 合理使用可升级读锁避免死锁
    • 保持锁的粒度适当
  4. 性能考虑:

    • 读写锁适用于读多写少的场景
    • 写操作频繁时性能可能不如简单锁
    • 可升级读锁有额外开销
  5. 注意事项:

    • 避免锁升级时的死锁
    • 正确处理异常以防止锁泄漏
    • 使用using语句或Dispose清理资源
posted @ 2025-08-27 01:24  jessqiu  阅读(41)  评论(0)    收藏  举报