C#如何实现一个多线程安全的无锁文件日志记录器

来源:建站教程作者:USDT程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《C#如何实现一个多线程安全的无锁文件日志记录器》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#如何实现一个多线程安全的无锁文件日志记录器》有用,将其分享出去将是对创作者最好的鼓励。

在多线程程序运行过程中,日志写入是非常高频的操作,如果采用传统的锁机制保证写入安全,很容易出现线程争抢锁资源导致的性能下降问题。无锁设计可以通过队列和原子操作避免锁的使用,同时保证日志写入的线程安全。

C#如何实现一个多线程安全的无锁文件日志记录器

核心设计思路

无锁文件日志记录器的核心设计分为三个部分:

  • 日志消息队列:使用线程安全的无锁队列存储待写入的日志内容,生产者线程将日志放入队列,消费者线程从队列取出日志写入文件。
  • 后台写入线程:单独启动一个后台线程作为消费者,持续从队列中获取日志并写入文件,避免业务线程被文件IO阻塞。
  • 原子状态控制:使用原子变量控制日志记录器的启动和停止状态,避免多线程操作状态时的冲突。

无锁队列的实现

C#中可以使用System.Collections.Concurrent命名空间下的ConcurrentQueue作为无锁队列,它是线程安全的先进先出集合,内部通过原子操作和自旋等待实现无锁入队出队。如果需要自定义无锁队列,也可以基于原子操作实现,以下是简单的自定义无锁队列示例:

using System;
using System.Threading;

namespace LockFreeLogger
{
    // 无锁队列节点定义
    internal class LockFreeQueueNode<T>
    {
        public T Value { get; set; }
        public LockFreeQueueNode<T> Next { get; set; }
    }

    // 无锁队列实现
    internal class LockFreeQueue<T>
    {
        private LockFreeQueueNode<T> _head;
        private LockFreeQueueNode<T> _tail;

        public LockFreeQueue()
        {
            // 初始化头尾节点为哨兵节点
            _head = _tail = new LockFreeQueueNode<T>();
        }

        // 入队操作,无锁
        public void Enqueue(T item)
        {
            var newNode = new LockFreeQueueNode<T> { Value = item };
            LockFreeQueueNode<T> oldTail;
            LockFreeQueueNode<T> oldNext;
            do
            {
                oldTail = _tail;
                oldNext = oldTail.Next;
                // 如果尾节点的下一个节点不为空,说明其他线程已经修改了尾节点,重新获取
                if (oldNext != null)
                {
                    Interlocked.CompareExchange(ref _tail, oldNext, oldTail);
                }
            } while (oldNext != null);
            // 尝试将新节点设置为尾节点的下一个节点
            if (Interlocked.CompareExchange(ref oldTail.Next, newNode, null) == null)
            {
                // 成功后将尾节点指向新节点
                Interlocked.CompareExchange(ref _tail, newNode, oldTail);
            }
        }

        // 出队操作,无锁
        public bool TryDequeue(out T result)
        {
            result = default;
            LockFreeQueueNode<T> oldHead;
            LockFreeQueueNode<T> oldTail;
            LockFreeQueueNode<T> oldNext;
            do
            {
                oldHead = _head;
                oldTail = _tail;
                oldNext = oldHead.Next;
                // 如果头节点等于尾节点,且下一个节点为空,说明队列为空
                if (oldHead == oldTail)
                {
                    if (oldNext == null)
                    {
                        return false;
                    }
                    // 尾节点落后,更新尾节点
                    Interlocked.CompareExchange(ref _tail, oldNext, oldTail);
                }
            } while (oldHead != oldTail || oldNext != null);
            // 取出节点值
            result = oldNext.Value;
            // 尝试将头节点移动到下一个节点
            if (Interlocked.CompareExchange(ref _head, oldNext, oldHead) == oldHead)
            {
                return true;
            }
            return false;
        }

        // 判断队列是否为空
        public bool IsEmpty()
        {
            return _head.Next == null;
        }
    }
}

日志记录器完整实现

基于无锁队列和后台写入线程,我们实现完整的日志记录器,以下是核心代码:

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

namespace LockFreeLogger
{
    public class LockFreeFileLogger : IDisposable
    {
        // 日志队列,存储待写入的日志内容
        private readonly LockFreeQueue<string> _logQueue = new LockFreeQueue<string>();
        // 日志记录器运行状态,使用原子操作保证线程安全
        private int _isRunning = 0;
        // 后台写入任务的取消令牌
        private CancellationTokenSource _cancellationTokenSource;
        // 日志文件路径
        private readonly string _logFilePath;
        // 后台写入线程
        private Task _writeTask;

        public LockFreeFileLogger(string logFilePath)
        {
            _logFilePath = logFilePath;
        }

        // 启动日志记录器
        public void Start()
        {
            // 原子操作设置运行状态为1,只有状态为0时才启动
            if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 0)
            {
                _cancellationTokenSource = new CancellationTokenSource();
                // 启动后台写入任务
                _writeTask = Task.Run(() => WriteLogLoop(_cancellationTokenSource.Token));
            }
        }

        // 停止日志记录器
        public void Stop()
        {
            // 原子操作设置运行状态为0
            if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1)
            {
                _cancellationTokenSource?.Cancel();
                // 等待后台任务完成剩余日志写入
                _writeTask?.Wait(TimeSpan.FromSeconds(5));
                _cancellationTokenSource?.Dispose();
            }
        }

        // 写入日志的公共方法,多线程调用安全
        public void Log(string message)
        {
            // 如果日志记录器未运行,直接返回
            if (Volatile.Read(ref _isRunning) == 0)
            {
                return;
            }
            // 拼接日志时间和内容
            string logContent = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}";
            // 无锁入队
            _logQueue.Enqueue(logContent);
        }

        // 后台写入循环
        private void WriteLogLoop(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested || !_logQueue.IsEmpty())
            {
                // 尝试从队列取出日志
                if (_logQueue.TryDequeue(out string logContent))
                {
                    try
                    {
                        // 写入文件,追加模式
                        using (var writer = new StreamWriter(_logFilePath, true))
                        {
                            writer.WriteLine(logContent);
                        }
                    }
                    catch
                    {
                        // 实际项目中可以添加写入失败的重试逻辑
                    }
                }
                else
                {
                    // 队列为空时短暂等待,避免空转消耗CPU
                    Thread.Sleep(10);
                }
            }
        }

        // 释放资源
        public void Dispose()
        {
            Stop();
        }
    }
}

使用示例

以下是多线程场景下使用该日志记录器的示例:

using System;
using System.Threading;

namespace LockFreeLogger
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化日志记录器,指定日志文件路径
            using (var logger = new LockFreeFileLogger("app.log"))
            {
                // 启动日志记录器
                logger.Start();

                // 启动10个线程同时写入日志
                for (int i = 0; i < 10; i++)
                {
                    int threadId = i;
                    Thread thread = new Thread(() =>
                    {
                        for (int j = 0; j < 100; j++)
                        {
                            logger.Log($"线程{threadId}写入第{j}条日志");
                        }
                    });
                    thread.Start();
                }

                // 等待所有线程执行完成
                Thread.Sleep(2000);
                // 停止日志记录器
                logger.Stop();
            }
            Console.WriteLine("日志写入完成");
        }
    }
}

注意事项

该无锁文件日志记录器虽然避免了锁的使用,但仍有需要注意的点:

  • 日志文件写入是IO操作,单线程写入可以保证日志顺序,如果需要更高的写入性能,可以考虑批量写入,减少IO次数。
  • 如果日志产生速度远超过写入速度,队列会不断堆积,需要添加队列长度限制,避免内存溢出。
  • 实际生产环境中需要添加日志分级、日志文件滚动、写入异常重试等更完善的功能。

C#无锁队列文件日志多线程安全日志记录器修改时间:2026-07-20 05:00:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。