C# 如何使类线程安全

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1344025/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:24:09  来源:igfitidea点击:

How to make a class Thread Safe

c#multithreadingthread-safety

提问by Ghassan Karwchan

I am writing a C# application. I have (a kind of) logging class. and this logging class will be used by many threads. How to make this class thread safe? Should I make it as singleton? what are the best practices there? Is there a document that I can read about how to make it thread-safe?

我正在编写一个 C# 应用程序。我有(一种)日志类。并且这个日志类将被许多线程使用。如何使此类线程安全?我应该让它成为单身人士吗?那里的最佳做法是什么?是否有我可以阅读有关如何使其成为线程安全的文档?

Thanks

谢谢

采纳答案by jeremyalan

In C#, any object can be used to protect a "critical section", or in other words, code that must not be executed by two threads at the same time.

在 C# 中,任何对象都可用于保护“临界区”,即不能同时由两个线程执行的代码。

For example, the following will synchronize access to the SharedLogger.Write method, so only a single thread is logging a message at any given time.

例如,以下将同步对 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;
}

回答by Nick

use lock()so that multiple threads will not use the logging at the same time

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

回答by BCS

  • Try and do most computation using local variables and then alter the state of the object in one quick locked block.
  • Keep in mind that some variables might change between when you read them and when you get to altering the state.
  • 尝试使用局部变量进行大多数计算,然后在一个快速lock编辑块中更改对象的状态。
  • 请记住,某些变量可能会在您阅读它们和您开始更改状态之间发生变化。

回答by captncraig

I would use an off the shelf logger for this, since there are several that are rock solid and simple to use. No need to roll your own. I reccomend Log4Net.

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

回答by Eric M

Pursuant to BCS' answer:

根据 BCS 的回答:

BCS is describing the case of a stateless object. Such an object is intrinsically thread safe because it has no variables of it's own that can be clobbered by calls from different theads.

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

The logger described does have a file handle (sorry, not C# user, maybe it's called IDiskFileResource or some such MS-ism) which it must serialize use of.

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

So, separate the storage of messages from the logic that writes them to the log file. The logic should only operate on one message at a time.

因此,将消息的存储与将它们写入日志文件的逻辑分开。逻辑应该一次只对一条消息进行操作。

One way to do it is: if the logger object were to keep a queue of message objects, and the logger object merely has the logic to pop a message off the queue, then extract the useful stuff from a message object, then write that to the log, then look for another message in the queue - then you can make that thread safe by having the queue's add/remove/queue_size/etc operations thread safe. It would require the logger class, a message class and a thread safe queue (which is probably a third class, an instance of which is a member variable of the logger class).

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

回答by Matt Davis

I'm not sure I can add anything to what has already been said about making a logging class thread-safe. As has been stated, to do this you must synchronize access to the resource, i.e., the log file, so that only one thread attempts to log to it at a time. The C# lockkeyword is the proper way to do this.

我不确定我可以在已经说过的关于使日志记录类线程安全的内容中添加任何内容。如前所述,要做到这一点,您必须同步对资源(即日志文件)的访问,以便一次只有一个线程尝试登录。C#lock关键字是执行此操作的正确方法。

However, I will address (1) the singleton approach and (2) the usability of the approach that you ultimately decide to use.

但是,我将讨论 (1) 单例方法和 (2) 您最终决定使用的方法的可用性。

(1) If your application writes all of its log messages to a single log file, then the singleton pattern is definitely the route to go. The log file will be opened at startup and closed at shutdown, and the singleton pattern fits this concept of operations perfectly. As @dtb pointed out, though, remember that making a class a singleton does not guarantee thread safety. Use the lockkeyword for that.

(1) 如果您的应用程序将其所有日志消息写入单个日志文件,那么单例模式绝对是要走的路线。日志文件将在启动时打开并在关闭时关闭,单例​​模式非常适合这种操作概念。但是,正如@dtb 指出的那样,请记住,将类设为单例并不能保证线程安全。lock为此使用关键字。

(2) As for the usability of the approach, consider this suggested solution:

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

Let me first say that this approach is generally ok. It defines a singleton instance of SharedLoggervia the Instancestatic variable and prevents others from instantiating the class by virtue of the private constructor. This is the essence of the singleton pattern, but I would strongly recommend reading and following Jon Skeet's advice regarding singletons in C#before going too far.

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

However, what I want to focus on is the usability of this solution. By 'usability,' I'm referring to the way one would use this implementation to log a message. Consider what the invocation looks like:

但是,我想关注的是该解决方案的可用性。通过“可用性”,我指的是人们使用此实现记录消息的方式。考虑一下调用的样子:

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

That whole 'Instance' part just looks wrong, but there's no way to avoid it given the implementation. Instead, consider this alternative:

整个“实例”部分看起来是错误的,但鉴于实现,没有办法避免它。相反,请考虑以下替代方案:

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

Notice that the class is now static, which means that all of its members and methods have to be static. It's not substantively different from the earlier example, but consider its usage.

请注意,该类现在是静态的,这意味着它的所有成员和方法都必须是静态的。它与前面的示例没有实质性的不同,但请考虑它的用法。

SharedLogger.Write("log message");

That's much simpler to code against.

对此进行编码要简单得多。

The point is not to denigrate the former solution, but to suggest that the usability of whatever solution you choose is an important aspect not to be overlooked. A good, usable API can make code simpler to write, more elegant, and easier to maintain.

重点不是贬低前一种解决方案,而是表明您选择的任何解决方案的可用性都是不容忽视的重要方面。一个好的、可用的 API 可以使代码编写更简单、更优雅、更易于维护。

回答by Sallac

in my opinion the provided code above is not thread safe any more: in the previous solution you had to instatiate a new object of SharedLogger and the method Write existed once for every object.

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

Now you just have one Write method, which is used by all threads, example:

现在您只有一个 Write 方法,所有线程都使用该方法,例如:

thread 1: SharedLogger.Write("Thread 1")

线程 1: SharedLogger.Write("线程 1")

thread 2: SharedLogger.Write("Thread 2");

线程 2: SharedLogger.Write("线程 2");

   public static void Write(string s)
   {
       // thread 1 is interrupted here <=
       lock (_lock)
       {
           _writer.Write(s);
       }
   }
  • thread 1 wants to write a message, but is interrupted by thread 2 (see comment)
  • thread 2 overrides the message of thread 1 and is interrupted by thread 1

  • thread 1 gets the lock and writes "Thread 2"

  • thread 1 releases the lock
  • thread 2 gets the lock and writes "Thread 2"
  • thread 2 releases the lock
  • 线程 1 想写一条消息,但被线程 2 中断(见注释)
  • 线程 2 覆盖线程 1 的消息并被线程 1 中断

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

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

correct me when I am wrong ...

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

回答by Eric Brown - Cal

If performance isn't' the main issue, for instance if the class isn't under a lot of load, just do this:

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

Make your class inherit ContextBoundObject

让你的类继承 ContextBoundObject

Apply this attribute to your class [Synchronization]

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

Your entire class is now only accessible to one thread at a time.

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

It's really more useful for diagnosis, as speed wise it's nearly the worst case...but to quickly determine "is this weird issue a racing condition", throw it on, re run the test.. if the problem goes away... you know it's a threading problem...

它对于诊断真的更有用,因为速度方面它几乎是最坏的情况......但是要快速确定“这个奇怪的问题是赛车状况吗”,把它扔掉,重新运行测试......如果问题消失......你知道这是一个线程问题......

A more performant option is to make your logging class have a thread safe message queue ( that accepts logging messages, then just pull them out and process them sequentially...

一个更高效的选择是让你的日志类有一个线程安全的消息队列(它接受日志消息,然后把它们拉出来并按顺序处理它们......

For example the ConcurrentQueue class in the new parallelism stuff is a good thread safe queue.

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

Or user log4net RollingLogFileAppender, which is already thread safe.

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