这个设计线程安全吗?

时间:2016-09-17 21:13:56

标签: c# concurrency thread-safety

在c#中,主类创建了一个Logger对象,许多线程都可以访问它。记录器对象看起来像(简化)

public sealed class Logger
{
    private ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

    public Logger()
    {
        // create other objects here AND a thread that extracts
        // from the queue and writes to a file
        // because queue is thread safe this is perfectly ok
    }

   public void Log(string whatToLog)
   {
       // Now, is this safe? This method will be called by several threads
       // perhaps at the same time

       string s = whatToLog + " " + DateTime.Now.ToString();
       queue.Enqueue(s);

       // The thread created in the constructor will extract and log
   }
}

从设计的角度来看,这样可以吗?我的两个问题是:

  • 是“string s = whatToLog +”“+ DateTime.Now.ToString();”好的,如果多个线程同时访问此方法?我想是的,因为任何线程都有自己的s副本,对吗?

  • 如果几个线程只使用Log()方法访问Logger对象,那么一切都安全吗?

由于

3 个答案:

答案 0 :(得分:1)

该类非常安全。

一些建议的改进。 该类不会阻止实例化多个实例,如果您希望所有线程都记录到同一个对象,这一点很重要。也许可以应用单身模式。使用静态构造函数的伪单例的快速示例。请注意,默认构造函数是私有的,阻止任何其他类创建记录器。

与性能相关的更改是避免在记录时连接字符串。创建新字符串并不是一个便宜的操作。此外,一旦DateTime.Now转换为字符串,评估就会困难得多。例如。按创建日期和时间等对消息进行排序。在下文中,whatToLog与元组中的DateTime.Now配对。

public sealed class Logger
{
    public static Logger instance {get; private set;}
    static Logger()
    {
        instance = new Logger();
    }

    private ConcurrentQueue<Tuple<string, DateTime>> queue = new ConcurrentQueue<Tuple<string, DateTime>>();
    private Logger() {}

    public void Log(string whatToLog)
    {
       queue.Enqueue(new Tuple(whatToLog, DateTime.Now));
    }
}

答案 1 :(得分:0)

ConcurrentQueue将确保队列部分是线程安全的。 您构造的字符串s不会使其或多或少地具有线程安全性

  • 在当前表单中,您应该实例化记录器,并将引用传递给将使用此类的每个线程
  • 虽然是线程安全的,但不保证项目的顺序性
  • 队列无法无限增长,请确保您的出队机制能够跟上

改进:

  • 使类静态,更容易访问多个线程
  • 对阅读和写作分开关注;这可以通过创建几个基本函数internal并将类放在同一个命名空间
  • 来完成
  • 使用C#6字符串插值

带改进的代码

public static class Logger
{
    private static ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

    public static void Log(string LogMessage)
    {
        // thread safe logging
        queue.Enqueue($"{LogMessage} {DateTime.Now}");
    }


    //dequeue only within namespace
    internal static string Dequeue() {
        string dequeuedItem;
        queue.TryDequeue(out dequeuedItem);
        return dequeuedItem;
    }
}

public class LoggerReader
{
    public LoggerReader()
    {
        // create other objects here AND a thread that extracts
        // from the queue and writes to a file
        // because queue is thread safe this is perfectly ok
        string logItem = Logger.Dequeue();
    }
}

答案 2 :(得分:-2)

我只是在Log方法中使用一个锁(用Queue替换ConcurrentQueue),不再担心每条指令,特别是如果原始记录器比这里的例子更复杂!

   public void Log(string whatToLog)
   {
       lock(queue) {
           string s = whatToLog + " " + DateTime.Now.ToString();
           queue.Enqueue(s);
       }    
   }