C#使用ReaderWriterLockSlim解决多线程并发写入文件冲突

巷口听风
巷口听风
发布时间:2026-10-14| 6人浏览过
原创

在C#程序开发中,多线程同时操作同一资源(如日志文件)时,极易触发 IOException 异常,提示“文件正在由另一进程使用”。这是因为操作系统默认不允许两个线程同时以独占模式打开同一个文件句柄。本文将演示如何利用 System.Threading.ReaderWriterLockSlim 类管理访问权限,解决这一并发写入难题。

核心原理与关键配置

ReaderWriterLockSlim 是微软提供的用于管理资源访问锁定状态的类,它允许多个线程进行读取或进行独占式写入。要解决并发写文件的问题,必须严格遵循以下规范:

  1. 静态实例化:不同线程若创建不同的 ReaderWriterLockSlim 对象来锁定同一个文件,系统会视为两把不同的锁,依然会导致冲突。因此,该对象必须定义为 static 只读实例,确保全局唯一。
  2. 正确获取与释放:调用 EnterWriteLock() 进入锁定状态;若需设置超时防止死锁,可使用 TryEnterWriteLock(timeout),超时返回 false。无论成功与否,退出锁定都必须调用 ExitWriteLock(),且必须在 finally 块中执行,以防因未处理的异常导致锁无法释放(死锁)或次数不匹配报错。

操作步骤与代码实现

第1步:复现多线程并发写入冲突

目的:展示不使用同步机制时,多线程并行写入导致的丢包和报错现象。

入口:新建一个 Program 类。

动作:使用 Parallel.For 模拟多个线程同时调用 WriteLog 方法向 log.txt 追加内容。

class Program
{
    static int LogCount = 100;
    static int WritedCount = 0;
    static int FailedCount = 0;
    
    static void Main(string[] args)
    {
        // 启动多线程并发写入
        Parallel.For(0, LogCount, e =>
        {
            WriteLog();
        });
        
        Console.WriteLine($"\r\nLog Count:{LogCount}.\t Writed Count:{WritedCount}.\t Failed Count:{FailedCount}.");
        Console.Read();
    }

    static void WriteLog()
    {
        try
        {
            var logFilePath = "log.txt";
            var now = DateTime.Now;
            var logContent = $"Tid: {Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4)} {now.ToLongDateString()} {now.ToLongTimeString()}.{now.Millisecond}\r\n";
            
            // 此处没有加锁,多线程直接写入同一文件
            File.AppendAllText(logFilePath, logContent);
            WritedCount++;
        }
        catch (Exception ex)
        {
            FailedCount++;
            Console.WriteLine(ex.Message); // 此处通常会打印“文件正在由另一进程使用...”错误
        }
    }
}

完成状态判断:观察控制台输出会发现 FailedCount 大于 0,且日志文件中存在部分丢失的现象。

第2步:引入读写锁同步写入

目的:通过添加 ReaderWriterLockSlim 将资源访问串行化,确保一次只有一个线程能写入文件。

参数设置:定义静态只读的锁对象 static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();。

动作:在 WriteLog 方法的 try 块开头申请写入锁,并在 finally 块中释放锁。

class Program
{
    static int LogCount = 100;
    static int WritedCount = 0;
    static int FailedCount = 0;
    
    // 关键:必须定义为静态对象,保证所有线程共用同一把锁
    static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();

    static void Main(string[] args)
    {
        Parallel.For(0, LogCount, e => {
            WriteLog();
        });
        Console.WriteLine($"\r\nLog Count:{LogCount}.\t Writed Count:{WritedCount}.\t Failed Count:{FailedCount}.");
        Console.Read();
    }

    static void WriteLog()
    {
        try
        {
            // 进入写入独占模式,其他线程请求写入将被阻塞
            LogWriteLock.EnterWriteLock();
            
            var logFilePath = "log.txt";
            var now = DateTime.Now;
            var logContent = $"Tid: {Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4)} {now.ToLongDateString()} {now.ToLongTimeString()}.{now.Millisecond}\r\n";
            
            File.AppendAllText(logFilePath, logContent);
            WritedCount++;
        }
        catch (Exception)
        {
            FailedCount++;
        }
        finally
        {
            // 必须释放锁,否则会引发死锁
            LogWriteLock.ExitWriteLock();
        }
    }
}

完成状态判断:再次运行,FailedCount 为 0,且生成的 log.txt 文件行数与设定的 LogCount 完全一致,证明数据未丢失且无冲突报错。

第3步:复杂多线程嵌套环境测试

目的:验证在 ThreadPool、新 Thread 以及 Parallel.For 混合使用的极端高并发场景下,静态读写锁的有效性。

操作:在 Main 方法中启动多个任务源(主线程池、后台线程等),总计发起大量并发写入请求。

class Program
{
    static int LogCount = 1000;
    static int SumLogCount = 0;
    static int WritedCount = 0;
    static int FailedCount = 0;
    // 沿用上述静态锁...

    static void Main(string[] args)
    {
        // 任务源1:线程池异步任务
        SumLogCount += LogCount;
        ThreadPool.QueueUserWorkItem((obj) =>
        {
            Parallel.For(0, LogCount, e => WriteLog());
        });

        // 任务源2:全新线程内的线程池任务
        SumLogCount += LogCount;
        var thread1 = new Thread(() =>
        {
            Parallel.For(0, LogCount, e =>
            {
                ThreadPool.QueueUserWorkItem((subObj) => WriteLog());
            });
        });
        thread1.IsBackground = false;
        thread1.Start();

        // 任务源3:直接线程池队列
        SumLogCount += LogCount;
        Parallel.For(0, LogCount, e =>
        {
            ThreadPool.QueueUserWorkItem((obj) => WriteLog());
        });

        // 任务源4:后台线程内循环
        SumLogCount += LogCount;
        var thread2 = new Thread(() =>
        {
            Parallel.For(0, LogCount, e => WriteLog());
        });
        thread2.IsBackground = false;
        thread2.Start();

        // 任务源5:当前主线程同步循环
        SumLogCount += LogCount;
        Parallel.For(0, LogCount, e => WriteLog());
        
        Console.WriteLine("Main Thread Processed.\r\n");
        
        // 简单轮询显示状态
        while(true)
        {
            Console.WriteLine($"Sum Log Count:{SumLogCount}.\t Writed Count:{WritedCount}.\t Failed Count:{FailedCount}.");
            if(WritedCount + FailedCount == SumLogCount) break;
            System.Threading.Thread.Sleep(1000);
        }
    }
    // ... WriteLog 方法同上 ...
}

检查最终输出的日志文件片段,可以看到不同线程ID(Tid)的日志条间隔有序地交错出现,没有任何报错信息,说明静态 ReaderWriterLockSlim 在处理复杂的跨线程池、跨层级并发调用时同样稳健。

简要总结

解决 C# 多线程并发写入文件的最佳实践是使用 ReaderWriterLockSlim 并配合 try-finally 语句块。务必将其声明为 static 对象以维持全局唯一的锁状态,从而避免“进程占用”异常,确保日志等共享资源数据的完整性与一致性。

以上就是C#使用读写锁三行代码简单解决多线程并发问题的详细内容,更多关于C#多线程开发的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。