ConcurrentDictionary最后,原子操作——有时需要Lock

本文关键字:Lock 原子操作 ConcurrentDictionary 最后 | 更新日期: 2023-09-27 18:04:04

假设我有这样一个简单的代码:(simplify)

其中MyConCurrentDictionary是静态的ConcurrentDictionary<string, string>(它驻留在不同的类中)。

/*1*/   public void Send(string Message, string UserName)
/*2*/   {            
/*3*/       string ConnectionId;
/*4*/       if (MyConCurrentDictionary.TryGetValue(UserName,out ConnectionId))
/*5*/       {
/*6*/       //...   
/*7*/           DB.InsertMessage( Message, ConnectionId);
/*8*/           LOG.LogMessage  ( Message, ConnectionId);
/*9*/       //...
/*10*/       }
/*11*/       else ...
/*12*/   }

此方法在多个实例中运行。(signalR hub,如果你愿意)

如果user/connectionID存在,我需要插入到DB/log ONLY

Ok,那么当多线程访问ConcurrentDictionary时,#4行是线程安全的

但是还有另一个方法RemoveUser -它从字典中删除用户:

 public void RemoveUser (string userName)
        {
           string removed;
           if ( MyConCurrentDictionary.TryRemove(userName,out removed ))
              Clients.All.removeClientfromChat(userName);
        }

但是上下文可能会发生在#5行,这将执行RemoveUser从ConcurrentDictionary中删除 UserName。

为了解决这个问题-代码应该是这样的:

lock(locker)
{
  if (MyConCurrentDictionary.TryGetValue(UserName,out ConnectionId))
   {
    //...
   }
}

完全违背了 ConcurrentDictionary的目的。

在多线程环境中做这些事情的正确方法是什么,同时仍然有ConcurrentDictionary的好处?

nb,是ConcurrentDictionary只有在字典内的操作才是线程安全的。但我想说的是,在特定的情况下,我失去了ConcurrentDictionary的好处,因为我仍然需要使用锁。(也许我错了)

ConcurrentDictionary最后,原子操作——有时需要Lock

根据您所写的内容和我目前的理解:ConcurrentDictionary不是合适的选择!

写一个类,封装一个普通的Dictionary,用ReaderWriterLockSlim封装读和写操作。这比普通的锁要快得多。你也可以同时有多个读操作,但只有一个写操作。

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
public void DoRead(string xyz)
{
    try
    {
        _lock.EnterReadLock();
        // whatever
    }
    finally
    {
        _lock.ExitReadLock();
    }
}
public void DoWrite(string xyz)
{
    try
    {
        _lock.EnterWriteLock();
        // whatever
    }
    finally
    {
        _lock.ExitWriteLock();
    }
}

看起来像是设计问题。

你有ConcurentDictionary,但你不自动使用它,有一个由几个命令组成的操作,所以你必须使用lock(或其他同步)。

最简单的解决方案是允许LOG具有已经删除的用户id的消息,换句话说,在处理LOG消息时处理此cas(从MyConcurentDictionary使用TryGetValue)。

我也可以考虑有两个集合:一个是有效的用户id,另一个是已删除的用户。当您想要添加到LOG时,则检查两个集合。当您想要删除用户时,然后将该用户添加到已删除用户集合中,并在一段时间后有效的集合中删除该用户(一些时间= 1秒或其他)。