我使用ConcurrentDictionary
在web api应用程序中收集内存中的数据。使用api方法,我在ConcurrentDictionary
中添加和更新对象。还有后台线程根据对象属性分析和清理这个字典。现在我正在考虑两种方法:
1。在updateValueFactory
方法的AddOrUpdate
中对字典项使用锁,但问题是如何正确读取属性以确保我有最新版本的那个和我'我不是在不稳定的状态下阅读财产
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);
}
这里的最佳做法是什么?
答案 0 :(得分:0)
因此,您的两个实现都存在重大问题。第一个解决方案在递增时锁定,但在读取时不锁定,这意味着访问数据的其他位置可以读取无效状态。
非技术性问题,但一个主要问题是,您已将您的班级命名为ThreadSaveItem
,但实际上它并非设计为可以安全地从多个线程访问。在此实现中,调用者责任是确保不从多个线程访问该项目。如果我看到一个名为ThreadSafeItem
的课程,我会认为可以安全地从多个主题访问它,并且我不需要同步我对它的访问权限。我执行的每个操作都是唯一需要逻辑原子的操作。
您的Interlocked
解决方案存在问题,因为您必须使用您正在修改的字段,这些字段在概念上绑定在一起,但您不能将其更改同步,这意味着有人可以观察到修改一个而不是另一个,这是该代码的问题。
接下来,在两种解决方案中使用AddOrUpdate
并不合适。方法调用的重点是添加一个项目或用另一个项目替换它,而不是改变提供的项目(这就是为什么它需要一个返回值;你应该生成一个新项目)。如果你想采用获取可变项并改变它的方法,那么可以通过调用GetOrAdd
来获取现有项或创建一个新项,然后在一个线程中对其进行变异使用返回值的安全方式。
只需简单地ThreadSafeItem
不可变,就可以彻底简化整个解决方案。它允许您在AddOrUpdate
上使用ConcurrentDictionary
进行更新,这意味着需要完成的唯一同步是更新ConcurrentDictionary
的值,并且它已经处理同步它自己的状态,在访问ThreadSafeItem
时根本不需要同步,因为对数据的所有访问都是固有的线程安全,因为它是不可变的。这意味着您根本不需要编写任何同步代码,这正是您希望尽可能争取的。
最后,我们有实际的代码:
public class ThreadsafeService3
{
private readonly ConcurrentDictionary<string, ThreadSafeItem3> _storage =
new ConcurrentDictionary<string, ThreadSafeItem3>();
public void AddOrUpdate(string name)
{
_storage.AddOrUpdate(name, _ => new ThreadSafeItem3(), (_, oldValue) => oldValue.Increment());
}
public void Analyze()
{
foreach (var pair in _storage)
{
long ticks = pair.Value.ModifiedTicks;
//Note, the value may have been updated since we checked;
//you've said you don't care and it's okay for a newer item to be removed here if it loses the race.
if (isTooOld(ticks))
_storage.TryRemove(pair.Key, out _);
}
}
}
public class ThreadSafeItem3
{
public ThreadSafeItem3()
{
Counter = 0;
}
private ThreadSafeItem3(int counter)
{
Counter = counter;
}
public ThreadSafeItem3 Increment()
{
return new ThreadSafeItem3(Counter + 1);
}
public long ModifiedTicks { get; } = DateTime.Now.Ticks;
public int Counter { get; }
}
答案 1 :(得分:-1)
我不能评论你究竟在做什么的具体细节,但是互锁和并发字典比你自己做的锁更好。
我会质疑这种方法。您的数据非常重要,但坚持下去并不是那么重要?根据应用程序的使用情况,这种方法会在一定程度上减慢速度。再一次,不知道你在做什么,你可以把每个“添加”扔进一个MSMQ,然后在一些时间表运行外部exe来处理这些项目。该网站只会触发并忘记,没有线程要求。