多线程的应用程序的互动记录器螺纹
-
06-07-2019 - |
题
我在这里再次与问题有关的多线程和行使我的 并行编程 类。
我有一个多线服务器实现的使用。净 异步编程模型 -有 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++; }
}
基本上,这里的要点这个代号:
- 开始一个低优先级 螺纹 是循环的 记录的队列 和打印未决的 日志 输出。在此之后, 螺纹 被暂停,直到新的 日志 到达;
- 当日志达,该记录器螺纹是唤醒和它的工作。
是这样的解决方案 线安全?我已经阅读 生产者-消费者 问题和解决方案算法,但是在这个问题,尽管我有多生产者,我只有一个读者。
预先感谢所有你的注意力。
解决方案
它似乎应当工作。生产者-消费者不应该改变在很大情况下的单个消费者。小nitpicks:
获取锁定可能是一个昂贵的运作(如@Vitaliy Lipchinsky说).我建议进行基准测试你的记录器对天真的写-通过记录仪和记录器使用的联锁行动。另一个替代办法将是交流现有的排队有空中
GetLog
和离开关键部分。这种方式没有生产者将不会阻止通过长期的行动在消费者。让LogObj基准类型(班)。有没有点在使它的结构,因为你是拳击呢。或者做别的
_queue
场的类型LogObj[]
(这是更好无论如何)。让你的纹的背景,以便它不会阻止关闭程序,如果
Stop
不会被称为。冲洗你的
TextWriter
.否则你冒着失去甚至这些记录,设法配合排队(10个项目是一点小恕我直言)实现IDisposable和/或终结.你的记录拥有线和文本作者和那些应该被释放(和刷新参见上文)。
其他提示
注意:只需阅读其他回复。以下是基于您自己的相当优化,乐观的锁定解决方案。主要区别在于锁定内部类,最小化“关键部分”,并提供优雅的线程终止。如果你想完全避免锁定,那么你可以尝试一些不稳定的“非锁定”。 @Vitaliy Lipchinsky建议链接列表。
using System.Collections.Generic;
using System.Linq;
using System.Threading;
...
public class Logger
{
// BEST PRACTICE: private synchronization object.
// lock on _syncRoot - you should have one for each critical
// section - to avoid locking on public 'this' instance
private readonly object _syncRoot = new object ();
// synchronization device for stopping our log thread.
// initialized to unsignaled state - when set to signaled
// we stop!
private readonly AutoResetEvent _isStopping =
new AutoResetEvent (false);
// use a Queue<>, cleaner and less error prone than
// manipulating an array. btw, check your indexing
// on your array queue, while starvation will not
// occur in your full pass, ordering is not preserved
private readonly Queue<LogObj> _queue = new Queue<LogObj>();
...
public void Log (string message)
{
// you want to lock ONLY when absolutely necessary
// which in this case is accessing the ONE resource
// of _queue.
lock (_syncRoot)
{
_queue.Enqueue (new LogObj (DateTime.Now, message));
}
}
public void GetLog ()
{
// while not stopping
//
// NOTE: _loggerThread is polling. to increase poll
// interval, increase wait period. for a more event
// driven approach, consider using another
// AutoResetEvent at end of loop, and signal it
// from Log() method above
for (; !_isStopping.WaitOne(1); )
{
List<LogObj> logs = null;
// again lock ONLY when you need to. because our log
// operations may be time-intensive, we do not want
// to block pessimistically. what we really want is
// to dequeue all available messages and release the
// shared resource.
lock (_syncRoot)
{
// copy messages for local scope processing!
//
// NOTE: .Net3.5 extension method. if not available
// logs = new List<LogObj> (_queue);
logs = _queue.ToList ();
// clear the queue for new messages
_queue.Clear ();
// release!
}
foreach (LogObj log in logs)
{
// do your thang
...
}
}
}
}
...
public void Stop ()
{
// graceful thread termination. give threads a chance!
_isStopping.Set ();
_loggerThread.Join (100);
if (_loggerThread.IsAlive)
{
_loggerThread.Abort ();
}
_loggerThread = null;
}
实际上,你在这里引入锁定。在将日志条目推送到队列时锁定(Log方法):如果10个线程同时将10个项目推入队列并唤醒Logger线程,则第11个线程将等待记录器线程记录所有项目...
如果你想要一些真正可扩展的东西 - 实现无锁队列(例如下面的例子)。使用无锁队列同步机制将非常直接(您甚至可以使用单个等待句柄进行通知)。
如果你无法在网上找到无锁队列实现,这里有一个想法如何做到这一点: 使用链接列表进行实施。链表中的每个节点都包含值和对下一个节点的易失性引用。因此,对于操作入队和出队,您可以使用Interlocked.CompareExchange方法。我希望,这个想法很清楚。如果没有 - 让我知道,我会提供更多细节。
我现在只是在做一个思考实验,因为我现在没有时间真正尝试代码,但我认为如果你有创意,你可以完全没有锁定。
让您的日志记录类包含一个方法,该方法在每次调用时分配队列和信号量(以及另一个在线程完成时释放队列和信号量的方法)。想要进行日志记录的线程在启动时会调用此方法。当他们想要记录时,他们将消息推送到他们自己的队列并设置信号量。记录器线程有一个大循环,它遍历队列并检查相关的信号量。如果与队列关联的信号量大于零,则弹出队列并减少信号量。
因为在设置信号量之前你没有尝试从队列中弹出东西,并且在你把东西推到队列之前你没有设置信号量,我想这将是安全的。根据队列类的MSDN文档,如果要枚举队列而另一个线程修改集合,则会引发异常。赶上那个例外,你应该做得很好。