多线程应用程序与记录器线程的交互

Xpi*_*itO 6 .net c# logging multithreading concurrent-programming

在这里,我再次提出有关多线程和我的并发编程类的练习的问题.

我有一个多线程服务器 - 使用.NET 异步编程模型实现 - 带GET(下载)和PUT(上传)文件服务.这部分已完成并经过测试.

碰巧问题的陈述说这个服务器必须具有对服务器响应时间影响最小的日志记录活动,并且应该由低优先级线程 - 记录器线程支持 - 为此效果创建.所有日志消息都应由生成它们的线程传递给此记录器线程,使用可能不会锁定调用它的线程的通信机制(除了必要的锁定以确保互斥)并假设某些日志消息可能被忽略.

这是我目前的解决方案,请帮助验证这是否是解决所述问题的方法:

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

// Multi-threaded Logger
public class Logger {
    // textwriter to use as logging output
    protected readonly TextWriter _output;
    // logger thread
    protected Thread _loggerThread;
    // logger thread wait timeout
    protected int _timeOut = 500; //500ms
    // amount of log requests attended
    protected volatile int reqNr = 0;
    // logging queue
    protected readonly object[] _queue;
    protected struct LogObj {
        public DateTime _start;
        public string _msg;
        public LogObj(string msg) {
            _start = DateTime.Now;
            _msg = msg;
        }
        public LogObj(DateTime start, string msg) {
            _start = start;
            _msg = msg;
        }
        public override string ToString() {
            return String.Format("{0}: {1}", _start, _msg);
        }
    }

    public Logger(int dimension,TextWriter output) {
        /// initialize queue with parameterized dimension
        this._queue = new object[dimension];
        // initialize logging output
        this._output = output;
        // initialize logger thread
        Start();
    }
    public Logger() {
        // initialize queue with 10 positions
        this._queue = new object[10];
        // initialize logging output to use console output
        this._output = Console.Out;
        // initialize logger thread
        Start();
    }

    public void Log(string msg) {
        lock (this) {
            for (int i = 0; i < _queue.Length; i++) {
                // seek for the first available position on queue
                if (_queue[i] == null) {
                    // insert pending log into queue position
                    _queue[i] = new LogObj(DateTime.Now, msg);
                    // notify logger thread for a pending log on the queue
                    Monitor.Pulse(this);
                    break;
                }
                // if there aren't any available positions on logging queue, this
                // log is not considered and the thread returns
            }
        }
    }

    public void GetLog() {
        lock (this) {
            while(true) {
                for (int i = 0; i < _queue.Length; i++) {
                    // seek all occupied positions on queue (those who have logs)
                    if (_queue[i] != null) {
                        // log
                        LogObj obj = (LogObj)_queue[i];
                        // makes this position available
                        _queue[i] = null;
                        // print log into output stream
                        _output.WriteLine(String.Format("[Thread #{0} | {1}ms] {2}",
                                                        Thread.CurrentThread.ManagedThreadId,
                                                        DateTime.Now.Subtract(obj._start).TotalMilliseconds,
                                                        obj.ToString()));
                    }
                }
                // after printing all pending log's (or if there aren't any pending log's),
                // the thread waits until another log arrives
                //Monitor.Wait(this, _timeOut);
                Monitor.Wait(this);
            }
        }
    }

    // Starts logger thread activity
    public void Start() {
        // Create the thread object, passing in the Logger.Start method
        // via a ThreadStart delegate. This does not start the thread.
        _loggerThread = new Thread(this.GetLog);
        _loggerThread.Priority = ThreadPriority.Lowest;
        _loggerThread.Start();
    }

    // Stops logger thread activity
    public void Stop() {
        _loggerThread.Abort();
        _loggerThread = null;
    }

    // Increments number of attended log requests
    public void IncReq() { reqNr++; }

}
Run Code Online (Sandbox Code Playgroud)

基本上,以下是此代码的要点:

  1. 启动一个低优先级线程,循环日志记录队列并将挂起的日志打印到输出.在此之后,线程被暂停直到新日志到达;
  2. 当日志到达时,记录器线程被唤醒并且它正常工作.

这个解决方案是线程安全的吗?我一直在阅读生产者 - 消费者问题和解决方案算法,但在这个问题上虽然我有多个生产者,但我只有一个读者.

提前感谢您的所有关注.

eld*_*rge 4

看来应该可以工作了。在单一消费者的情况下,生产者-消费者不应发生太大变化。小挑剔:

  • 获取锁可能是一项昂贵的操作(正如@Vitaliy Lipchinsky 所说)。我建议将您的记录器与天真的“直写”记录器和使用互锁操作的记录器进行基准测试。另一种选择是将现有队列与空队列交换GetLog并立即离开关键部分。这样,任何生产者都不会被消费者的长时间操作所阻塞。

  • 使 LogObj 引用类型(类)。使它成为结构是没有意义的,因为无论如何你都在装箱。或者使_queue字段成为类型LogObj[](无论如何,这样更好)。

  • Stop让你的线程成为后台,这样如果不被调用,它就不会阻止关闭你的程序。

  • 冲洗你的TextWriter. 否则,即使是那些能够容纳队列的记录,您也会面临丢失的风险(恕我直言,10 项有点小)

  • 实现 IDisposable 和/或终结器。您的记录器拥有线程和文本编写器,这些应该被释放(并刷新 - 见上文)。