交换缓冲区对并发访问的影响

时间:2013-09-13 21:53:36

标签: multithreading concurrency thread-safety

考虑具有两个线程的应用程序, Producer Consumer

两个线程的运行速度大致相同,一次多次。

两个线程都访问相同的内存区域,其中 Producer 写入内存, Consumer 读取当前的数据块并对其执行某些操作,而不会使数据无效。

这是一个经典的方法:

int[] sharedData;

//Called frequently by thread Producer
void WriteValues(int[] data) 
{
    lock(sharedData) 
    {
        Array.Copy(data, sharedData, LENGTH);
    }
}

//Called frequently by thread Consumer
void WriteValues() 
{
    int[] data;

    lock(sharedData) 
    {
        Array.Copy(sharedData, data, LENGTH);
    }

    DoSomething(data);
}

如果我们假设Array.Copy需要时间,那么此代码将运行缓慢,因为Producer总是必须在复制期间等待Consumer,反之亦然。

解决这个问题的方法是创建两个缓冲区,一个由Consumer访问,另一个由Producer写入,并在写入完成后交换缓冲区。

int[] frontBuffer;
int[] backBuffer;

//Called frequently by thread Producer
void WriteValues(int[] data) 
{
    lock(backBuffer) 
    {
        Array.Copy(data, backBuffer, LENGTH);

        int[] temp = frontBuffer;
        frontBuffer = backBuffer;
        backBuffer = temp;
    }
}

//Called frequently by thread Consumer
void WriteValues() 
{
    int[] data;

    int[] currentFrontBuffer = frontBuffer;

    lock(currentForntBuffer) 
    {
        Array.Copy(currentFrontBuffer , data, LENGTH);
    }

    DoSomething(currentForntBuffer );
}

现在,我的问题:

  1. 锁定,如第二个例子所示,安全吗?或者引用的变化是否会引入问题?
  2. 第二个示例中的代码执行速度是否比第一个示例中的代码快?
  3. 有没有更好的方法可以有效地解决上述问题?
  4. 如果没有锁,有没有办法解决这个问题? (即使我认为这是不可能的)
  5. 注意:这不是经典的生产者/消费者问题:Consumer可能会在Producer再次写入之前多次读取这些值 - 旧数据在Producer写入新数据之前一直有效。

1 个答案:

答案 0 :(得分:1)

  

锁定,如第二个例子所示,安全吗?或者引用的更改是否会引入问题?

据我所知,因为引用赋值是原子的,所以这可能是安全但不理想。由于WriteValues()方法从frontBuffer读取而没有锁定或内存屏障强制缓存刷新,因此无法保证变量将使用主内存中的新值进行更新。然后有可能从本地寄存器或CPU缓存中连续读取该实例的陈旧缓存值。我不确定编译器/ JIT是否可能根据局部变量推断出缓存刷新,也许具有更多特定知识的人可以对这个区域说话。

即使值不是陈旧的,您也可能遇到比您想要的更多争用。例如......

  • 主题A调用WriteValues()
  • 线程A锁定frontBuffer中的实例并开始复制。
  • 主题B调用WriteValues(int[])
  • 线程B写入其数据,将当前锁定的frontBuffer实例移动到backBuffer
  • 主题B调用WriteValues(int[])
  • 线程B等待backBuffer的锁定,因为线程A仍然拥有它。
  

第二个例子中的代码执行速度会比第一个例子中的代码快吗?

我建议您对其进行分析并找出答案。如果Y对于你的特殊需求而言太慢,那么X比Y快,这是唯一的问题,你是唯一一个知道它们是什么的人。

  

有没有更好的方法可以有效地解决上述问题?

是。如果您使用的是.Net 4及更高版本,System.Collections.Concurrent中的BlockingCollection类型可以很好地模拟Producer / Consumer模式。如果你一直阅读的内容比你写的多,或者对很少的作者有多个读者,你可能还想考虑ReaderWriterLockSlim类。作为一般的经验法则,你应该尽可能少地锁定,这也有助于缓解你的时间问题。

  

如果没有锁定,有没有办法解决这个问题? (即使我认为这是不可能的)

您可能会这样做,但除非您非常熟悉多线程,缓存一致性以及潜在的编译器/ JIT优化,否则我不建议您尝试这样做。锁定很可能适用于您的情况,并且您(和其他人阅读您的代码)更容易 来推理和维护。