如何使用MemoryBarrier?确保原子性操作安全的方法
本文关键字:操作 安全 方法 原子性 确保 何使用 MemoryBarrier | 更新日期: 2023-09-27 18:13:03
当我在学习线程内存屏障(栅栏)似乎真的不容易理解,在这里,在我的情况下,我想要雇员10个线程同时增加一个Int32数字:x在每个(x++) 100倍,并将得到结果10 * 100 = 1000。
所以这实际上是一个原子性问题,据我所知,目前有很多方法可以实现这一点-限制在并发方式:
- 联锁。增加
- 独占锁(锁、监视器、互斥锁、信号量等)
- ReadWriteLockSlim
如果有更好的方法,请指导我,我试图使用易失性读/写,但失败了:
for (int i = 0; i < 10000; i++)
{
Thread.VolatileRead(ref x);
Thread.VolatileWrite(ref x, x + 1);
}
我的调查代码整理如下:
private const int MaxThraedCount = 10;
private Thread[] m_Workers = new Thread[MaxThraedCount];
private volatile int m_Counter = 0;
private Int32 x = 0;
protected void btn_DoWork_Click(object sender, EventArgs e)
{
for (int i = 0; i < MaxThraedCount; i++)
{
m_Workers[i] = new Thread(IncreaseNumber) { Name = "Thread " + (i + 1) };
m_Workers[i].Start();
}
}
void IncreaseNumber()
{
try
{
for (int i = 0; i < 10000; i++)
Interlocked.Increment(ref x);
// Increases Counter and decides whether or not sets the finish signal
m_Counter++;
if (m_Counter == MaxThraedCount)
{
// Print finish information on UI
m_Counter = 0;
}
}
catch (Exception ex)
{
throw;
}
}
我的问题是:我怎么能使用内存屏障来取代Interlocked,因为"所有的Interlocked的方法生成一个完整的栅栏",我试图修改增加循环如下,但失败了,我不明白为什么…
for (int i = 0; i < 10000; i++)
{
Thread.MemoryBarrier();
x++;
Thread.MemoryBarrier();
}
内存屏障只是防止内存操作从屏障的一侧移动到另一侧。你的问题是:
- 线程A读取x的值线程B读取x的值
- 线程A在它读取的值上加1。
- 线程B给它读取的值加1。
- 线程A回写它计算的值。
- 线程B回写它计算的值。
哎呀,两次加1只加1。内存屏障不是原子操作,也不是锁。它们只是强制排序,而不是强制原子性。
不幸的是,x86架构不提供任何不包含完整栅栏的原子操作。事情就是这样。好的一面是,完整的围栏经过了大量优化。(例如,它从不锁定任何总线)不能"用MemoryBarrier
代替Interlocked
"。它们是两种不同的工具。
使用MemoryBarrier
, volatile
等来控制读和写的重排序。使用Interlocked
, lock
等来实现原子性。
(此外,您是否知道调用MemoryBarrier
也会生成一个完整的fence*,就像VolatileRead
和VolatileWrite
一样?因此,如果您出于性能原因试图避免Interlocked
, lock
等,那么您的替代方案很有可能性能更低以及更多可能被破坏。
*至少在标准的Microsoft CLR中是这样。我不确定Mono等