svi*_*ick 5 .net c# thread-safety lockless
我有一个通用字段和一个封装它的属性:
T item;
public T Item
{
get { return item; }
set { item = value; }
}
Run Code Online (Sandbox Code Playgroud)
问题是这个属性可以从一个线程写入并同时从多个线程读取.如果T是a struct,或者long,读者可能得到的结果是旧的价值和新的价值.我怎么能防止这种情况?
我尝试过使用volatile,但这是不可能的:
易失性字段不能是"T"类型.
由于这是一个更简单的代码,我已经编写过了,所以ConcurrentQueue<T>我也想过在这里使用它:
ConcurrentQueue<T> item;
public T Item
{
get
{
T result;
item.TryPeek(out result);
return item;
}
set
{
item.TryEnqueue(value);
T ignored;
item.TryDequeue(out ignored);
}
}
Run Code Online (Sandbox Code Playgroud)
这可行,但在我看来,这是一个过于简单的解决方案.
性能很重要,因此,如果可能,应避免锁定.
如果a set同时发生get,我不关心是get返回旧值还是新值.
这完全取决于类型T。
如果您能够施加class限制,T那么在这种特殊情况下您不需要执行任何操作。 引用分配是原子的。这意味着您不能对基础变量进行部分或损坏的写入。
阅读也是如此。您将无法阅读部分编写的参考文献。
如果T是一个结构,那么只有以下结构可以被原子地读取/分配(根据 C# 规范的第 12.5 节,强调我的,也证明了上述声明的合理性):
以下数据类型的读取和写入应是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。此外,对前面列表中的基础类型的枚举类型的读取和写入也应该是原子的。其他类型(包括 long、ulong、double 和decimal)以及用户定义类型的读取和写入不需要是原子的。除了为此目的而设计的库函数之外,不保证原子读取-修改-写入,例如在递增或递减的情况下。
因此,如果您所做的只是尝试读/写,并且满足上述条件之一,那么您不必执行任何操作(但这意味着您还必须对 type 施加约束T)。
如果您不能保证 的约束T,那么您将不得不诉诸类似lock语句的方式来同步访问(对于前面提到的读取和写入)。
如果您发现使用lock语句(实际上是Monitor类)会降低性能,那么您可以使用SpinLock结构Monitor,因为它旨在帮助处理过于繁重的地方:
T item;
SpinLock sl = new SpinLock();
public T Item
{
get
{
bool lockTaken = false;
try
{
sl.Enter(ref lockTaken);
return item;
}
finally
{
if (lockTaken) sl.Exit();
}
}
set
{
bool lockTaken = false;
try
{
sl.Enter(ref lockTaken);
item = value;
}
finally
{
if (lockTaken) sl.Exit();
}
}
}
Run Code Online (Sandbox Code Playgroud)
但要小心,如果等待时间过长,性能SpinLock可能会下降,并且会与类相同Monitor;当然,考虑到您使用的是简单的赋值/读取,它不应该花费太长时间(除非您使用的结构由于复制语义而尺寸很大)。
当然,您应该自己测试一下您预测将使用此类的情况,并查看哪种方法最适合您(lock或SpinLock结构)。
| 归档时间: |
|
| 查看次数: |
689 次 |
| 最近记录: |