【发布时间】:2017-11-01 14:15:07
【问题描述】:
我使用ConcurrentDictionary 在 web api 应用程序中收集内存中的数据。我使用 api 方法在ConcurrentDictionary 中添加和更新对象。并且有后台线程根据对象属性分析和清理这个字典。现在我正在考虑两种方法:
1. 在AddOrUpdate 方法中使用updateValueFactory 中的字典项锁定,但问题是如何正确读取属性以确保我拥有最新的它的版本,并且我没有在不稳定状态下读取属性
public class ThreadsafeService2
{
private readonly ConcurrentDictionary<string, ThreadSafeItem2> _storage =
new ConcurrentDictionary<string, ThreadSafeItem2>();
public void AddOrUpdate(string name)
{
var newVal = new ThreadSafeItem2();
_storage.AddOrUpdate(name, newVal, (key, oldVal) =>
{
//use lock
lock (oldVal)
{
oldVal.Increment();
}
return oldVal;
});
}
public void Analyze()
{
foreach (var key in _storage.Keys)
{
if (_storage.TryGetValue(key, out var item))
{
//how to read it properly?
long ticks = item.ModifiedTicks;
}
}
}
}
public class ThreadSafeItem2
{
private long _modifiedTicks;
private int _counter;
public void Increment()
{
//no interlocked here
_modifiedTicks = DateTime.Now.Ticks;
_counter++;
}
//now interlocked here
public long ModifiedTicks => _modifiedTicks;
public int Counter => _counter;
}
2.在没有锁定的属性级别上使用Interlocked 和memory barriers,对我来说看起来有点冗长
public class ThreadsafeService1
{
private readonly ConcurrentDictionary<string, ThreadSafeItem1> _storage =
new ConcurrentDictionary<string, ThreadSafeItem1>();
public void AddOrUpdate(string name)
{
var newVal = new ThreadSafeItem1();
_storage.AddOrUpdate(name, newVal, (key, oldVal) =>
{
//no lock here
oldVal.Increment();
return oldVal;
});
}
public void Analyze()
{
foreach(var key in _storage.Keys)
{
if(_storage.TryGetValue(key, out var item))
{
//reading through interloacked
long ticks = item.ModifiedTicks;
}
}
}
}
public class ThreadSafeItem1
{
private long _modifiedTicks;
private int _counter;
public void Increment()
{
//make changes in atomic manner
Interlocked.Exchange(ref _modifiedTicks, DateTime.Now.Ticks);
Interlocked.Increment(ref _counter);
}
public long ModifiedTicks => Interlocked.Read(ref _modifiedTicks);
public int Counter => Thread.VolatileRead(ref _counter);
}
这里的最佳做法是什么?
【问题讨论】:
-
我认为这取决于
Analyze对这些值的作用。在您阅读long ticks = item.ModifiedTicks之后使用第二种方法,此值可能已经过时(另一个线程可能在您阅读后已经更新了它)。根据您使用这些值的方式,这可能是一个问题。 -
@Evk 我将它与某个阈值进行比较并决定:我是否应该将其从字典中删除。此外,在工作项目中,我在 ThreadSafeItem 中还有另一种方法,它根据计数器和 modifiedticks 值返回布尔值。
-
因此,如果您只增加修改的刻度并根据“ModifiedTicks > 阈值”删除项目 - 您并不关心“陈旧”的刻度值。另一方面,如果您根据“ModifiedTicks ModifiedTicks 之后,另一个线程可能会增加它。在这种情况下,您需要锁定整个操作(读取和删除)。如果您不确定 - 锁定整个操作。锁定只是阅读本身通常没有什么意义。
-
@mtkachenko Evk 是完全正确的。由于您正在根据获取的数据修改正在同步的数据,因此整个操作在逻辑上是关键部分的一部分。您应该只使用
Dictionary,而不是ConcurrentDictionary,并锁定整个操作以更新值以及整个操作以分析该值。出于多种原因,您当前的解决方案都不安全。 -
@Evk 此背景检查计划每 10 分钟运行一次,我可以阅读并做出决定 - 我是否应该删除它。如果某个线程稍后会更新它,那就太晚了。我只想确保以一致且线程安全的方式读取和写入 item 中的属性。
标签: c# multithreading concurrency