2009-08-27 163 views
17

我正在编写一个C#应用程序。 我有(一种)日志类。并且这个日志记录类将被许多线程使用。 如何使这个类线程安全? 我应该让它成为单身? 那里有哪些最佳实践? 是否有我可以阅读的关于如何使其线程安全的文档?如何制作线程安全

感谢

+7

的Singleton模式并不意味着线程安全。 – dtb 2009-08-27 22:24:47

+2

首先仔细定义“线程安全”的含义。人们使用这个术语就像它意味着某种特定的事物,事实上,它只是意味着“在场景X中正常工作”。如果没有“正确”的规范和X是什么的陈述,你实际上不能实现某些东西,并且知道你解决了一个真正存在的问题。 – 2009-08-27 22:47:12

+1

请查看Joseph Albahari的[这篇文章](http://www.albahari.com/threading/part2.aspx#_ThreadSafety)。大约一半的方式进入文章是保持第二行记上“线程安全” – 2013-01-04 06:48:05

回答

15

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

就是一个很好的例子。 – Maciek 2009-08-27 22:28:28

+0

这很好,但你可以让_lock和_writer成为静态而不必处理使这个事情成为单例。但使用现有的记录器仍然会更容易。 – 2009-08-27 22:29:37

+3

我同意使用现有的记录器实现,但如果他在处理多线程代码,他最终将需要知道如何正确同步访问各种资源,而这个同样的过程可以在其他应用... – jeremyalan 2009-08-27 22:32:57

6
  • 尝试多个线程将不使用记录和使用本地变量做大部分的计算,然后改变对象的状态一个快速lock ed块。
  • 请记住,一些变量可能会在您阅读它们和改变状态之间发生变化。
+0

+1很大的部分,我浪费了2周理解是 – 2013-06-12 14:00:18

9

我会使用现成的记录仪,因为有几个坚如磐石,使用简单。无需推出自己的。我建议Log4Net.

+0

log4net的是伟大的,我已经使用了多次,并一直与快乐它。 – 2009-08-27 22:30:10

+10

我同意这种记录。但它没有回答这个问题。 – 2009-08-27 22:44:00

+0

Log4Net是shiznit!上面的代码是 – Crackerjack 2012-04-06 22:40:16

2

根据BCS”的答案:

BCS被描述无国籍物体的情况下。这样一个对象本质上是线程安全的,因为它没有它自己的变量,可以被来自不同的角色的调用破坏。

描述的记录器确实有一个文件句柄(对不起,不是C#用户,也许它被称为IDiskFileResource或一些这样的MS-ISM),它必须序列化使用。

因此,将消息的存储与将它们写入日志文件的逻辑分开。逻辑一次只能处理一条消息。

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

+0

另一种选择(在某些情况下)是让记录器调用在本地存储器中生成完整记录,然后在一次调用中将其写入输出流。 IIRC大多数操作系统都提供了一个系统调用,它可以在任何情况下(几乎?)对流进行原子写入,但资源耗尽,然后还有其他问题。 – BCS 2010-11-15 21:22:10

8

我不知道我可以添加任何东西,已经说过关于使一个日志类线程安全。如前所述,为此,您必须同步对资源(即日志文件)的访问,以便一次只有一个线程尝试登录。 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; 
} 

让我先说,这种方法一般是OK。它通过Instance静态变量定义了一个单例实例SharedLogger,并通过私有构造函数阻止其他实例化类。这是单身模式的本质,但我强烈建议阅读并遵循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可以使代码更容易编写,更优雅,更易于维护。

+0

我同意这一点......更不用说废话了。 – 2012-01-19 01:32:44

+0

尽可能使用静态类作为经验法则。 – 2012-01-19 01:33:03

+0

我们不能只用lock(this)而不是lock(_this)? (使用即时作为锁定对象?) – 2013-06-12 14:07:43

1

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

现在你只需要一个Write方法,用于所有线程,例如:

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

线程2: SharedLogger。写(“线程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释放锁

纠正我,当我错了......

0

如果性能”的主要问题,例如,如果该类不是下了不少负载的,只是这样做:

让你的类继承ContextBoundObject

这个属性应用到你的类[同步]

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

对于诊断来说它确实更加有用,因为速度方面它几乎是最糟糕的情况......但要快速确定“这是一个奇怪的问题是一个赛车状况”,将其抛出,重新运行测试..如果问题出现了......你知道这是一个线程的问题...

更高性能的选择是让你的日志类有一个线程安全的消息队列(接受日志消息,然后才拉出来,并依次对其进行处理.. 。

例如,在新的平行的东西ConcurrentQueue类是一个很好的线程安全的队列。

或用户log4net RollingLogFileAppender,它已经是thread safe