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

Roy*_*mir 2 c# multithreading locking .net-4.5

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

 public static ConcurrentDictionary<int, List<MSG>> DIC_PROFILEID__MSGS  = new ConcurrentDictionary...
Run Code Online (Sandbox Code Playgroud)

所以方法看起来像:

/*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*/   }
Run Code Online (Sandbox Code Playgroud)

请注意lockat #6.我这样做是因为如果2线程在#10,它们都会导致创建一个新的List(这是坏的).

但我对lock"太多" 的事实感到困扰.

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

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

Dictionary <int , object> DicLocks = new Dictionary <int , object>();
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,用户可以在不干扰的情况下将消息插入到不同的离线用户.

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

2)我真的很讨厌锁定ConcurrentDictionary,这是100%不对.我应该定期dictionary吗?

Sco*_*ain 5

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

您可以从第二个锁定对象获得的一个优点是,如果您要进行大量读取但写入很少,则可以使用a 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,我仍然会建议ConcurrentQueue<MSG>采用这种方法.