如何使类线程安全

我正在写一个C#应用程序。 我有(一种)日志记录类。 并且许multithreading将使用此日志记录类。 如何使这个类线程安全? 我应该把它作为单身人士吗? 有什么最好的做法? 是否有一个文件,我可以阅读有关如何使其线程安全?

谢谢

在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; } 

我会使用现成的记录器,因为有几个坚如磐石,易于使用。 无需自己动手。 我推荐Log4Net。

我不确定我是否可以添加任何有关使日志记录类线程安全的内容。 如前所述,要执行此操作,您必须同步对资源(即日志文件)的访问权限,以便一次只有一个线程尝试登录它。 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; } 

我先说这种方法通常没问题。 它通过Instance静态变量定义SharedLogger的单例实例,并防止其他人通过私有构造函数实例化该类。 这是单身模式的本质,但我强烈建议在走得太远之前阅读并遵循Jon Skeet关于C#中单身人士的建议。

但是,我想要关注的是这个解决方案的可用性。 通过’可用性’,我指的是使用此实现来记录消息的方式。 考虑一下调用的样子:

 SharedLogger.Instance.Write("log message"); 

整个’Instance’部分只是看起来不对,但考虑到实现,没有办法避免它。 相反,请考虑以下选择:

 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可以使代码编写更简单,更优雅,更易于维护。

  • 尝试使用局部变量进行大多数计算,然后在一个快速lock块中更改对象的状态。
  • 请记住,某些变量可能会在您阅读它们和更改状态之间发生变化。

使用lock()使多个线程不会同时使用日志记录

根据BCS的回答:

BCS描述了无状态对象的情况。 这样的对象本质上是线程安全的,因为它没有自己的变量,可以被来自不同theads的调用破坏。

所描述的记录器确实有一个文件句柄(抱歉,不是C#用户,也许它叫做IDiskFileResource或某些这样的MS-ism),它必须序列化使用。

因此,将消息的存储与将其写入日志文件的逻辑分开。 逻辑应该一次只能在一条消息上运行。

一种方法是:如果记录器对象要保留消息对象的队列,并且记录器对象仅具有从队列中弹出消息的逻辑,则从消息对象中提取有用的东西,然后将其写入日志,然后在队列中查找另一条消息 – 然后您可以通过使队列的add / remove / queue_size / etc操作线程安全来使该线程安全。 它需要logger类,消息类和线程安全队列(可能是第三类,其实例是logger类的成员变量)。

在我看来,上面提供的代码不再是线程安全的:在之前的解决方案中,您必须为SharedLogger创建一个新对象,并为每个对象设置一次Write方法。

现在你只有一个Write方法,它被所有线程使用,例如:

线程1:SharedLogger.Write(“线程1”)

线程2:SharedLogger.Write(“线程2”);

  public static void Write(string s) { // thread 1 is interrupted here <= lock (_lock) { _writer.Write(s); } } 
  • 线程1想要写一条消息,但被线程2中断(见注释)
  • 线程2覆盖线程1的消息并被线程1中断

  • 线程1获取锁并写入“线程2”

  • 线程1释放锁
  • 线程2获取锁并写入“线程2”
  • 线程2释放锁

当我错的时候纠正我...

如果性能不是’主要问题,例如,如果该类没有大量负载,请执行以下操作:

使您的类inheritanceContextBoundObject

将此属性应用于您的class级[同步]

您的整个class级现在一次只能访问一个线程。

这对于诊断来说真的更有用,因为速度方面它几乎是最糟糕的情况……但要迅速确定“这是一个奇怪的问题是一个竞赛条件”,扔掉它,重新运行测试……如果问题消失了……你知道这是一个线程问题……

更高效的选项是使您的日志记录类具有线程安全消息队列(接受日志消息,然后将它们拉出并按顺序处理它们…

例如,新并行化内容中的ConcurrentQueue类是一个很好的线程安全队列。

或用户log4net RollingLogFileAppender,它已经是线程安全的 。