锁对象的字典,以减少 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
吗?
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
对于高容量,还可以使用服务总线类型模式跨越线程边界,为每个用户创建一个队列,并在用户重新联机时使用推送消息。