如何使用MemoryBarrier?确保原子性操作安全的方法

本文关键字:操作 安全 方法 原子性 确保 何使用 MemoryBarrier | 更新日期: 2023-09-27 18:13:03

当我在学习线程内存屏障(栅栏)似乎真的不容易理解,在这里,在我的情况下,我想要雇员10个线程同时增加一个Int32数字:x在每个(x++) 100倍,并将得到结果10 * 100 = 1000。

所以这实际上是一个原子性问题,据我所知,目前有很多方法可以实现这一点-限制在并发方式:

  1. 联锁。增加
  2. 独占锁(锁、监视器、互斥锁、信号量等)
  3. 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();
}

如何使用MemoryBarrier?确保原子性操作安全的方法

内存屏障只是防止内存操作从屏障的一侧移动到另一侧。你的问题是:

    线程A读取x的值线程B读取x的值
  1. 线程A在它读取的值上加1。
  2. 线程B给它读取的值加1。
  3. 线程A回写它计算的值。
  4. 线程B回写它计算的值。

哎呀,两次加1只加1。内存屏障不是原子操作,也不是锁。它们只是强制排序,而不是强制原子性。

不幸的是,x86架构不提供任何不包含完整栅栏的原子操作。事情就是这样。好的一面是,完整的围栏经过了大量优化。(例如,它从不锁定任何总线)

不能"用MemoryBarrier代替Interlocked "。它们是两种不同的工具。

使用MemoryBarrier, volatile等来控制读和写的重排序。使用Interlocked, lock等来实现原子性。

(此外,您是否知道调用MemoryBarrier也会生成一个完整的fence*,就像VolatileReadVolatileWrite一样?因此,如果您出于性能原因试图避免Interlocked, lock等,那么您的替代方案很有可能性能更低以及更多可能被破坏。

*至少在标准的Microsoft CLR中是这样。我不确定Mono等