我对C#的理解说(感谢Jeff Richter和Jon Skeet),任务是“原子的”。什么不是当我们混合阅读&写入(递增/递减)因此我们需要在Interlocked上使用方法。如果只有Read& assign这两个操作都是原子的吗?
public class Xyz
{
private volatile int _lastValue;
private IList<int> AvailableValues { get; set; }
private object syncRoot = new object();
private Random random = new Random();
//Accessible by multiple threads
public int GetNextValue() //and return last value once store is exhausted
{
//...
var count = 0;
var returnValue = 0;
lock (syncRoot)
{
count = AvailableValues.Count;
}
if (count == 0)
{
//Read... without locking... potential multiple reads
returnValue = _lastValue;
}
else
{
var toReturn = random.Next(0, count);
lock (syncRoot)
{
returnValue = AvailableValues[toReturn];
AvailableValues.RemoveAt(toReturn);
}
//potential multiple writes... last writer wins
_lastValue = returnValue;
}
return returnValue;
}
public class Xyz
{
private volatile int _lastValue;
private IList<int> AvailableValues { get; set; }
private object syncRoot = new object();
private Random random = new Random();
//Accessible by multiple threads
public int GetNextValue() //and return last value once store is exhausted
{
//...
var count = 0;
var returnValue = 0;
lock (syncRoot)
{
count = AvailableValues.Count;
}
if (count == 0)
{
//Read... without locking... potential multiple reads
returnValue = _lastValue;
}
else
{
var toReturn = random.Next(0, count);
lock (syncRoot)
{
returnValue = AvailableValues[toReturn];
AvailableValues.RemoveAt(toReturn);
}
//potential multiple writes... last writer wins
_lastValue = returnValue;
}
return returnValue;
}
答案 0 :(得分:18)
我对C#的理解说(感谢 杰夫里希特&amp; Jon Skeet)那个 赋值是“原子的”。
一般来说,赋值不是原子的。 C#规范小心地调出了保证原子的东西。见5.5节:
以下数据类型的读写是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型。此外,在先前列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不保证是原子的。
(强调补充。)
如果只有Read&amp;分配两者 这些操作是原子的?
同样,第5.5节回答了你的问题:
无法保证原子读取 - 修改 - 写入
答案 1 :(得分:11)
volatile
实际上与缓存(寄存器等)更相关;使用volatile
,您知道该值实际上是从内存中立即写入/读取 (实际上并非总是如此)。这允许不同的线程立即看到彼此的更新。指令重新排序还有其他微妙的问题,但这很复杂。
这里要考虑“原子”的两个含义:
Double
的一半,产生一个从未存在的数字)“自身”取决于价值的大小;可以在一次操作中更新吗?读/写对更多地与隔离有关 - 即防止丢失更新。
在您的示例中,两个线程可以读取相同的_lastValue
,两者都进行计算,然后(单独)更新_lastValue
。其中一个更新将丢失。实际上,我希望您希望在读/写过程的持续时间上lock
。
答案 2 :(得分:5)
使用volatile关键字不会使访问线程安全,它只是确保从内存中读取变量,而不是从可能从先前读取缓存的寄存器中读取。某些体系结构会进行此优化,这会导致在多个线程写入同一变量的情况下使用过时值。
为了正确同步访问权限,您需要更广泛的锁定:
public class Xyz
{
private volatile int _lastValue;
private IList<int> AvailableValues { get; set; }
private object syncRoot = new object();
private Random rand = new Random();
//Accessible by multiple threads
public int GetNextValue() //and return last value once store is exhausted
{
//...
lock (syncRoot)
{
var count = AvailableValues.Count;
if(count == 0)
return _lastValue;
toReturn = rand.Next(0, count);
_lastValue = AvailableValues[toReturn];
AvailableValues.RemoveAt(toReturn);
}
return _lastValue;
}
}
如果性能是一个问题,您可能需要考虑使用LinkedList for AvailableValues,因为它支持O(1)删除操作。
答案 3 :(得分:2)
对于.Net 2.0及之前的版本,有一个名为ReaderWriterLock的类,它允许您单独阻止写入和读取。可能会有帮助。
对于.Net 3.5及更高版本,请考虑ReaderWriterLockSlim,微软会这样描述;
ReaderWriterLockSlim类似于ReaderWriterLock,但它简化了递归规则以及升级和降级锁定状态的规则。 ReaderWriterLockSlim避免了许多潜在的死锁案例。此外,ReaderWriterLockSlim的性能明显优于ReaderWriterLock。建议将ReaderWriterLockSlim用于所有新开发。
答案 4 :(得分:0)
不保证这(原子性)。
答案 5 :(得分:0)
它们适用于某些类型link。在你的情况下,它是一个int,所以根据C#规范,它是原子的。但正如本主题中的其他人一样,它并不保证您的代码是线程安全的。