我正在编写一个C#应用程序。 我有(一种)日志记录类。并且许多线程将使用此日志记录类。 如何使这个类线程安全? 我应该把它作为单身人士吗? 有什么最好的做法? 有没有关于如何使其成为线程安全的文档?
由于
答案 0 :(得分:18)
在C#中,任何对象都可以用来保护“关键部分”,换句话说,就是不能同时由两个线程执行的代码。
例如,以下内容将同步对SharedLogger.Write方法的访问,因此在任何给定时间只有一个线程正在记录消息。
public class SharedLogger : ILogger
{
public static SharedLogger Instance = new SharedLogger();
public void Write(string s)
{
lock (_lock)
{
_writer.Write(s);
}
}
private SharedLogger()
{
_writer = new LogWriter();
}
private object _lock;
private LogWriter _writer;
}
答案 1 :(得分:11)
我不确定我是否可以添加任何有关使日志记录类线程安全的内容。如上所述,为此,您必须同步对资源(即日志文件)的访问,以便一次只有一个线程尝试登录它。 C#lock
关键字是执行此操作的正确方法。
但是,我将讨论(1)单身方法和(2)您最终决定使用的方法的可用性。
(1)如果您的应用程序将其所有日志消息写入单个日志文件,那么单例模式肯定是要走的路。日志文件将在启动时打开并在关闭时关闭,单例模式完全符合这种操作概念。正如@dtb指出的那样,请记住,将类设为单例并不能保证线程安全。请使用lock
关键字。
(2)至于方法的可用性,请考虑这个建议的解决方案:
public class SharedLogger : ILogger
{
public static SharedLogger Instance = new SharedLogger();
public void Write(string s)
{
lock (_lock)
{
_writer.Write(s);
}
}
private SharedLogger()
{
_writer = new LogWriter();
}
private object _lock;
private LogWriter _writer;
}
首先我要说这种做法一般都可以。它通过SharedLogger
静态变量定义Instance
的单例实例,并防止其他人通过私有构造函数实例化该类。这是单身人士模式的本质,但我强烈建议在走得太远之前阅读并遵循Jon Skeet关于singletons in C#的建议。
但是,我想要关注的是这个解决方案的可用性。通过'可用性',我指的是使用此实现来记录消息的方式。考虑一下调用的内容:
SharedLogger.Instance.Write("log message");
整个'实例'部分只是看起来不对,但鉴于实施,没有办法避免它。相反,请考虑这个替代方案:
public static class SharedLogger : ILogger
{
private static LogWriter _writer = new LogWriter();
private static object _lock = new object();
public static void Write(string s)
{
lock (_lock)
{
_writer.Write(s);
}
}
}
请注意,该类现在是静态的,这意味着它的所有成员和方法都必须是静态的。它与前面的例子没有实质性的不同,但考虑它的用法。
SharedLogger.Write("log message");
这对代码来说简单得多。
关键不是诋毁前一种解决方案,而是建议您选择的任何解决方案的可用性是一个不容忽视的重要方面。一个好的,可用的API可以使代码编写更简单,更优雅,更易于维护。
答案 2 :(得分:8)
我会使用现成的记录器,因为有几个坚如磐石且易于使用。无需自己动手。我推荐Log4Net.
答案 3 :(得分:6)
lock
ed块中更改对象的状态。答案 4 :(得分:3)
使用lock()以便多个线程不会同时使用日志记录
答案 5 :(得分:2)
根据BCS的回答:
BCS正在描述无状态对象的情况。这样的对象本质上是线程安全的,因为它没有自己的变量,可以被来自不同theads的调用破坏。
描述的记录器确实有一个文件句柄(抱歉,不是C#用户,也许它叫做IDiskFileResource或某些这样的MS-ism),它必须序列化使用。
因此,将消息的存储与将其写入日志文件的逻辑分开。逻辑应该一次仅对一条消息进行操作。
一种方法是:如果记录器对象要保留消息对象的队列,并且记录器对象只有从队列中弹出消息的逻辑,那么从消息对象中提取有用的东西,然后将其写入日志,然后在队列中查找另一条消息 - 然后您可以通过使队列的add / remove / queue_size / etc操作线程安全来使该线程安全。它需要logger类,消息类和线程安全队列(可能是第三类,其实例是logger类的成员变量)。
答案 6 :(得分:1)
在我看来,上面提供的代码不再是线程安全的: 在上一个解决方案中,您必须为SharedLogger创建一个新对象,并为每个对象设置一次Write方法。
现在你只有一个Write方法,所有线程都使用它,例如:
主题1: SharedLogger.Write(“Thread 1”)
主题2: SharedLogger.Write(“Thread 2”);
public static void Write(string s)
{
// thread 1 is interrupted here <=
lock (_lock)
{
_writer.Write(s);
}
}
线程2覆盖线程1的消息并被线程1中断
线程1获取锁并写入“线程2”
当我错了时纠正我......
答案 7 :(得分:1)
如果表现不是&#39;主要问题,例如,如果班级没有很多负荷,只需这样做:
让你的类继承ContextBoundObject
将此属性应用于您的班级[同步]
现在,您的整个课程一次只能访问一个帖子。
对于诊断来说它确实更有用,因为速度方面它几乎是最糟糕的情况......但是要快速确定&#34;这个奇怪的问题是一个竞赛条件&#34;,抛出它在,重新运行测试..如果问题消失了......你知道它是一个线程问题......
更高效的选项是让您的日志记录类具有线程安全消息队列(接受日志消息,然后将它们拉出并按顺序处理它们......
例如,新并行化内容中的ConcurrentQueue类是一个很好的线程安全队列。
或用户log4net RollingLogFileAppender,已经是thread safe。