我不确定这是一个如此简单的问题,正如它在When should I use ConcurrentDictionary and Dictionary?中所述。
在我的例子中,我只有一个线程调用Add来添加项,而有几个线程调用Remove来删除项(每个条目一个,实际上是Timer)。有很多阅读线索。此外,我的逻辑确保被删除的项目不再被使用(基于时间的密钥),即它不会读取它们。
此外,它应该处理一个非常高的输入率。
因此,也许Dictionary在这里更好,因为它具有更好的性能,并且在相同的资源上没有真正的并发写入(我不确定)。毕竟,它不是一个链接列表类型的集合,需要在任何更改时更新项之间的指针。并发Add和Get在非线程安全Dictionary的情况下可能是错误的。
换句话说,Add操作是否非原子操作,因为Get操作对于同一密钥的Get操作会返回一个buggy对象,因为Dictionary有一个不稳定的状态?也许只同步Remove操作就足够了?
更新:注意到,我只有一个线程添加了项,几个计时器线程删除了每个项目(必须是一个已经添加的项),而Get操作是对时间范围后面的项目(要删除的项目)执行的而不是操作。另外,单个添加线程会生成一个已添加项的列表,以供处理读取线程读取。这就是为什么我认为它可以在没有任何锁的情况下工作(或者只对删除操作进行锁定)。
发布于 2022-05-01 07:20:07
我认为您可以对此进行优化,以减少字典中的锁数量。关键是找到一个条件来本地化一个时间间隔,在其中您修改字典,并确保在更新字典时不会超出此间隔。
例如,假设您需要10分钟来更新字典。你决定每天10点到11点之间更新字典。你可以在10点完成更新。
然后,从第二天的11:00到10:00,你可以使用你的字典而不加任何锁。在10:00至11:00之间,您必须始终使用锁,因为您的字典可能会更改。
在本例中,不可能在没有锁的情况下修改字典,因为只有在需要更新字典超过1小时时才会发生这种情况。
因此,锁是那个小时内的一个真正的锁,DateTime检查其余的23个小时。我不知道你是否能使你的情况适应这个焦点,但如果你能,你肯定会得到更好的表现。
您可以将此逻辑封装在方法中:
private void DoWithDictionary(Action action)
{
var now = DateTime.UtcNow;
if (now.Hour == 10)
{
action();
}
else
{
lock (this._dict)
{
action();
}
}
}
private T DoWithDictionary<T>(Func<T> action)
{
var now = DateTime.UtcNow;
if (now.Hour == 10)
{
return action();
}
else
{
lock (this._dict)
{
return action();
}
}
}并使用它:
var keyToGet = 17;
var value = DoWithDictionary(() => this._dict[keyToGet]);
DoWithDictionary(() =>
{
if (this._dict.TryGetValue(keyToGet, out string text))
{
//...
}
});
var keyToRemove = 17;
DoWithDictionary(() => this._dict.Remove(keyToRemove));更新
我认为我的回应被误解了。最后,类似于排队更改,每次执行都是在一个批中。
例如,假设一个线程在第一秒钟执行4次删除,在下一秒钟执行2次删除。那条线可以锁6个锁。线程可能有一个列表,其中保存所有人每秒钟删除一次。因此,在第一秒钟,线程添加了4个项。当第二个完成时,线程锁定字典,并仅用一个锁删除4个项。在下一秒中,为这2项删除一个额外的锁。所以,两个锁而不是六个锁。
我认为这是合理的,这是我通常做的,以获得更好的表现。此外,通过数据库访问o套接字发送:添加信息,如果缓冲区已满或过期,则发送所有信息。
这里的问题是,您有很多线程:您可以使用相同的方法,但只能在线程级别获得更好的性能。如果您能够获得一些逻辑(比如我的23/1小时,这只是一个微不足道的示例,在现实世界中使用起来很困难),那么您可以避免所有线程中的锁,从而获得更好的性能。最后,执行锁以获得对资源的独占访问。在我的示例中,我知道我可以在没有锁的情况下访问(而不是修改)字典,而且它是线程安全的,因为我知道(没有真正的锁语句)在这些小时内访问字典是安全的。
https://stackoverflow.com/questions/72074284
复制相似问题