我喜欢在C#中创建类的快速方法,我们可以轻松地创建成员而无需实现get和set代码,但(据我所知)它们不是线程安全的!
public class SocksEntry
{
public int Int1 { get; set; }
public int Int2 { get; set; }
}
C#是否提供了一种快速简单的方法来增加线程安全性而不必这样做?
public class SocksEntry
{
protected object _lock = new object();
// internal
private int _int1 = 0;
private int _int2 = 0;
// accessors
protected int Int1
{
get { lock (_lock) return _int1; }
set { lock (_lock) _int1 = value; }
}
protected int Int2
{
get { lock (_lock) return _int2; }
set { lock (_lock) _int2 = value; }
}
}
与非线程安全版本相比,它显然使整个类更大,创建更痛苦!
答案 0 :(得分:7)
使类线程安全比你想象的要困难得多。将锁定放在getter和setter周围可能是不够的。因此,最佳做法是让类不是线程安全的,并且如果他需要线程安全,则将责任留给此类的使用者以同步对它的访问。如果消费者不需要线程安全,那么很好,你不会因为你在没有需要的情况下将线程安全性构建到类中而惩罚应用程序的性能。为什么你认为.NET中99.99%的类不是线程安全的?
答案 1 :(得分:1)
答案 2 :(得分:0)
我认为您所指的线程安全问题是这样的:
public int Value { get; set; }
public void Foo()
{
if (this.Value > 0) // Read the property and make a decision based on its present value
{
// Do something with the property, assuming its value has passed your test.
// Note that the property's value may have been changed by another thread between the previous line and this one.
Console.WriteLine(this.Value);
}
}
问题是,在您检查属性值和使用它时,属性值可能已经发生了变化。无论您使用自动属性,后备变量还是问题中的锁定,都会发生这种情况。正确的解决方案取决于您需要应用程序的行为方式(如果值在一行与另一行之间发生变化,应该怎么做)。一个常见的解决方案是将值正确地缓存在您正在使用它的函数中:
public int Value { get; set; }
public void Foo()
{
int cachedValue = this.Value;
if (cachedValue > 0)
{
Console.WriteLine(cachedValue );
}
}
但是,对于您的应用应该做的事情,这不一定是正确的。
答案 3 :(得分:0)
最简单,然后唯一正确的方法是创建一个类immutable,因此只有读取操作可用。
public sealed class SocksEntry
{
public int Int1 { get; private set; }
public int Int2 { get; private set; }
}
但是,如果某个特定的业务实体无法做到这一点 - 暴露一些业务方法而不是属性设置器,那么同步整个方法(想想事务)而不是每个属性设置器有时候更容易理解设置
// lock entire business transaction if possible
public void UpdateEntity(int a, int b)
{
lock (entityLock)
{
Int1 = a;
Int2 = b;
}
}
答案 4 :(得分:0)
在.NET内存模型中,读取和写入小于或等于平台指针大小的本机整数类型(即32位int
,64位long
)是原子的。原子意味着您可以在不使用锁的情况下读取和写入它,并且您永远不会读取或写入不完整的(即写入之间的中间值)。
但是,.NET并不保证写入会立即对其他线程可见(这意味着您可以在线程A中写入,然后在线程B中读取,并仍然读取旧值)。在x86上,您可以立即查看体系结构,但在其他平台上,您需要在写入后使用volatile
字段或Thread.MemoryBarrier
。 lock
为您插入内存屏障,因此您无需担心。
另一件需要考虑的事情是你如何使用这个类。虽然单个访问是原子的,但如果要将多个字段作为一个原子操作读取或写入,则仍然需要lock
。 (好吧,技术上并不总是 - 你有时可以进行某些优化,但在你学习的时候坚持使用简单的东西)
简而言之:lock
存在相当大的问题 - 如果您需要立即可见,您可以摆脱它并使用volatile /障碍。