锁对象的字典,以减少 C# 中的延迟

本文关键字:延迟 对象 字典 | 更新日期: 2023-09-27 18:35:43

在线用户向离线用户发送消息时,我会将这些消息保存在ConcurrentDictionary中。 每个用户都在自己的Task(线程)中运行/旋转。

 public static ConcurrentDictionary<int, List<MSG>> DIC_PROFILEID__MSGS  = new ConcurrentDictionary...

所以该方法看起来像:

/*1*/   public static void SaveLaterMessages(MSG msg)
/*2*/   {
/*3*/       var dic = Globals.DIC_PROFILEID__MSGS;
/*4*/   
/*5*/   
/*6*/       lock (saveLaterMssagesLocker)
/*7*/       {
/*8*/           List<MSG> existingLst;
/*9*/           if (!dic.TryGetValue(msg.To, out existingLst))
/*10*/           {                                                  
/*11*/               existingLst = new List<MSG>();
/*12*/               dic.TryAdd(msg.To, existingLst);
/*13*/           }
/*14*/           existingLst.Add(msg);
/*15*/       }
/*16*/   }

请注意#6 lock。我这样做是因为如果2线程处于#10,它们都会导致创建新List(这很糟糕)。

但我对我lock"太多"这一事实感到困扰。

换句话说,如果我向offline-user-20发送消息,就没有理由不能向offline-user-22发送消息。

所以我正在考虑创建额外的锁字典:

Dictionary <int , object> DicLocks = new Dictionary <int , object>();

其中int键是userID

稍后,用new Object()初始化每个条目

所以现在我的方法看起来像:

public static void SaveLaterMessages(MSG msg)
{
    var dic = Globals.DIC_PROFILEID__MSGS;
    lock (Globals.DicLocks[msg.To]) //changed here !!!
    {
        List<MSG> existingLst;
        if (!dic.TryGetValue(msg.To, out existingLst))
        {                                                  
            existingLst = new List<MSG>();
            dic.TryAdd(msg.To, existingLst);
        }
        existingLst.Add(msg);
    }
}

现在,用户可以将消息插入到不同的离线用户而不会干扰。

问题

1)我采用这种方法是对的,还是有更好的方法?

2)我真的很讨厌锁在ConcurrentDictionary周围,这是100%不对的。我应该把它变成一个常规dictionary吗?

锁对象的字典,以减少 C# 中的延迟

ConcurrentDictonary有工具来帮助你解决你所处的情况,如果你将现有列表的检索/创建切换到单线程安全操作,它会使问题变得简单得多。

public static void SaveLaterMessages(MSG msg)
{
    var dic = Globals.DIC_PROFILEID__MSGS;
    List<MSG> existingLst = dic.GetOrAdd(msg.To, (key) => new List<MSG>());
    lock(((ICollection)existingLst).SyncRoot)
    {
        existingLst.Add(msg);
    }
}

这会尝试从字典中获取列表,如果它不存在,则创建一个新列表,然后它仅在添加到列表对象本身的列表的非线程安全操作上锁定。

如果可能的话,更好的选择是将List<MSG>替换为线程安全集合(如 ConcurrentQueue<MSG>),您根本不需要执行任何锁定(执行此操作的能力完全取决于消息在列表中后的使用方式)。如果确实需要使用不需要Globals.DicLocks[msg.To]来锁定的列表,则完全可以锁定从集合返回的列表对象。

您可以从第二个锁定对象中获得的一个优点是,如果您要进行大量读取但写入很少,则可以使用ReaderWriterLockSlim允许多个并发读取器,但只能使用一个写入器。

public static void SaveLaterMessages(MSG msg)
{
    var dic = Globals.DIC_PROFILEID__MSGS;
    List<MSG> existingLst = dic.GetOrAdd(msg.To, (key) => new List<MSG>());
    var lockingObj = GetLockingObject(existingLst);
    lockingObj.EnterWriteLock();
    try
    {
        existingLst.Add(msg);
    }
    finally
    {
        lockingObj.ExitWriteLock();
    }
}
private static ConcurrentDictionary<List<MSG>, ReaderWriterLockSlim> _msgLocks = new ConcurrentDictionary<List<MSG>, ReaderWriterLockSlim>();
public static ReaderWriterLockSlim GetLockingObject(List<MSG> msgList)
{
    _msgLocks.GetOrAdd(msgList, (key) => new ReaderWriterLockSlim());
}

//Elsewhere in multiple threads.
public MSG PeekNewestMessage(int myId)
{
    var dic = Globals.DIC_PROFILEID__MSGS;
    var list = dic[myId];
    var lockingObj = GetLockingObject(list);
    lockingObj.EnterReadLock();
    try
    {
        return list.FirstOrDefault();
    }
    finally
    {
        lockingObj.ExitReadLock();
    }
}

但是,我仍然建议使用ConcurrentQueue<MSG>方法而不是这种方法。

您还可以将"List()"代码包装在一个具有单例模式的类中,该模式管理每个用户列表的生存期并从中添加/删除,然后您的并发字典和支持代码将变得无锁。您的"List()"也可以是一个并发队列,并进一步减少您需要为将来添加等锁定所做的工作。

http://msdn.microsoft.com/en-us/library/dd267265(v=vs.110).aspx

对于高容量,还可以使用服务总线类型模式跨越线程边界,为每个用户创建一个队列,并在用户重新联机时使用推送消息。