【问题标题】:thread safety of a static class静态类的线程安全
【发布时间】:2012-09-22 02:56:36
【问题描述】:

我想用 c# 在我的 windows 应用程序中创建自己的事件系统。为此,我编写了以下类:

internal class EventManager
{
    private static List<EventRecord> s_listEvents = new List<EventRecord>();

    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        s_listEvents.Add(record);
    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        var r = (from l in s_listEvents
                 where l.EventDate >= date
                 select l).ToList<EventRecord>();
        return r;
    }
}

我想确保 EventManager 类是线程安全的。因为我要在我的应用程序中同时创建数百个线程。所有线程很可能会使用这个类来生成事件。并且GetRecordsByDate函数可能会在从不同线程调用AddEvent函数时从类外部调用。

简单地说,你能告诉我这种设计适合多线程windows应用程序吗?如果这不是线程安全的,那么我怎样才能使我的类或其成员线程安全?我应该使用同步对象来锁定整个 EventManager 类还是应该使用 readwritelocker 来锁定我的 s_listEvents 静态成员?

【问题讨论】:

  • 数百个线程并不理想。即使被阻塞,线程也相对昂贵。
  • 我的应用程序必须通过 tcp 从远程机器收集数据。并且这种收集操作必须在给定的持续时间内完成。所以我必须创建很多线程。我可以通过硬件负载平衡(比如使用多个服务器)来减少线程数,但在所有情况下,我的应用程序都将是多线程应用程序。
  • 为什么需要创建自己的事件系统?
  • 以下链接会很有用:stackoverflow.com/questions/1344025/…
  • @AlexandreVinçon 我将为事件系统添加一些功能。这就是我想控制事件逻辑的方式。

标签: c# multithreading static-methods


【解决方案1】:

由于类是静态的,您应该锁定 s_listEvents 成员。调用者很有可能无法访问共享锁对象,除非您将锁作为 EventManager 本身(或任何其他静态类)上的静态成员提供。如果是这种情况,您不妨直接在 EventManager 中实现对 s_listEvents 访问权限的锁定。这样可以避免调用者忘记获取锁的问题。

reader/writerlock 似乎是一个不错的选择。

【讨论】:

  • 我不太确定 ReaderWriter 锁是这里的最佳选择。作者会很快,读者会慢很多。它需要测试。
【解决方案2】:

您应该使用ConcurrentBag&lt;T&gt;,而不是使用List&lt;T&gt;。

ConcurrentBag 是一种线程安全的包实现,针对同一线程将同时生产和使用包中存储的数据的场景进行了优化。

更多信息:

http://msdn.microsoft.com/en-us/library/dd381779.aspx

另外,请注意创建要访问的线程数,超过 100 个线程会降低性能,因为切换上下文需要时间。

编辑:对于 .NET 3.5,您可以使用简单的 lock 来实现线程安全

internal class EventManager
{
    private static List<EventRecord> s_listEvents = new List<EventRecord>();
    private static object _syncObject = new object();


    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        lock(_syncObject)
        {
           s_listEvents.Add(record); 
        }

    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        lock (_syncObject)
        {
             var r = (from l in s_listEvents
                 where l.EventDate >= date
                 select l).ToList<EventRecord>();

             return r;
        }

    }
}

编辑:

根据您的情况,如果您非常频繁地读取数据,使用ReaderWriterLockSlim 和ReaderWriterLock 对整个应用程序会更好,因为它允许多个线程读取数据。

如果没有,请使用lock,总体上性能更好。

查看链接:

http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

【讨论】:

  • 谢谢,但 ConcurrentBag 似乎不适用于 .net framework 3.5。因为我使用的是框架 3.5,所以我不能使用 ConcurrentBag 实现。或者我应该自己实现这个逻辑。
  • @Dmitry 哪个比较理想,ReaderWriterLock 还是锁定_syncObject 对象?
  • @Fer:ReaderWriterLock 在lock 上没有获得太多性能,使用lock 会更简单且更具可读性
【解决方案3】:

你可以使用ReaderWriterLock类:

internal class EventManager
{
    static ReaderWriterLock rwl = new ReaderWriterLock();

    private static List<EventRecord> s_listEvents = new List<EventRecord>();

    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        rwl.AcquireWriterLock(0);
        try
        {
            s_listEvents.Add(record);
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        rwl.AcquireReaderLock(0);
        try
        {
            var r = (from l in s_listEvents
                     where l.EventDate >= date
                     select l).ToList<EventRecord>();
            return r;
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }
}

【讨论】:

    【解决方案4】:

    以下链接会很有用:

    How to make a class Thread Safe

    private object _lock;
    
    public static void AddEvent(EventRecord record)
    {
        lock (_lock)
        {
            record.EventDate = DateTime.Now;
            s_listEvents.Add(record);
        }
    }
    

    【讨论】:

      【解决方案5】:

      您的问题的最基本答案如下:为了使您的解决方案线程安全,您必须保护您的数据存储不被同时访问。这是通过在访问列表的任何点锁定您的列表来完成的。这意味着当您对列表进行迭代、添加或删除时,您必须锁定该区域。

      即使您正在访问这么多服务器,您也可能不想生成 100 多个线程,而是可能想要使用线程池,请参阅http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.90).aspx 了解详细信息。这将为您提供一个线程池,用于简单的“签入 - 下载数据 - 签出”任务,就像您所描述的那样。

      在编写多线程应用程序时,考虑底层存储的使用模式很重要。如果您的应用程序每秒将执行数百次添加,您可能需要考虑拥有一个底层数据结构的只读副本,这样您就不会在每次尝试按日期获取记录时阻塞整个系统。详细介绍见Intel's Optimization Guide

      【讨论】:

      • 考虑拥有数据结构的只读副本似乎很好的性能方面。我会阅读并考虑您的建议。谢谢。
      猜你喜欢
      • 2015-03-24
      • 2013-05-08
      • 1970-01-01
      • 2014-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多