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

Multithreading 交换缓冲区对并发访问的影响,multithreading,concurrency,thread-safety,Multithreading,Concurrency,Thread Safety,考虑一个有两个线程的应用程序,生产者和消费者 两个线程的运行频率大致相同,一秒钟内运行多次 两个线程都访问同一个内存区域,其中生产者写入内存,消费者读取当前数据块并对其执行操作,而不会使数据无效 一个经典的方法是: int[]共享数据; //线程生成器经常调用 无效写值(int[]数据) { 锁(共享数据) { 复制(数据、共享数据、长度); } } //线程使用者经常调用 void WriteValues() { int[]数据; 锁(共享数据) { 复制(共享数据、数据、长度); } 剂量测

考虑一个有两个线程的应用程序,生产者和消费者

两个线程的运行频率大致相同,一秒钟内运行多次

两个线程都访问同一个内存区域,其中生产者写入内存,消费者读取当前数据块并对其执行操作,而不会使数据无效

一个经典的方法是:

int[]共享数据;
//线程生成器经常调用
无效写值(int[]数据)
{
锁(共享数据)
{
复制(数据、共享数据、长度);
}
}
//线程使用者经常调用
void WriteValues()
{
int[]数据;
锁(共享数据)
{
复制(共享数据、数据、长度);
}
剂量测定(数据);
}
如果我们假设
Array.Copy
需要时间,那么这段代码会运行得很慢,因为生产者在复制过程中总是要等待消费者,反之亦然

解决此问题的一种方法是创建两个缓冲区,一个由使用者访问,另一个由生产者写入,并在写入完成后交换缓冲区

int[]frontBuffer;
int[]backBuffer;
//线程生成器经常调用
无效写值(int[]数据)
{
锁(后缓冲)
{
Array.Copy(数据、backBuffer、长度);
int[]temp=frontBuffer;
前缓冲区=后缓冲区;
backBuffer=温度;
}
}
//线程使用者经常调用
void WriteValues()
{
int[]数据;
int[]currentFrontBuffer=frontBuffer;
锁定(currentForntBuffer)
{
复制(currentFrontBuffer、数据、长度);
}
剂量测定法(currentForntBuffer);
}
现在,我的问题是:

  • 如第二个示例所示,锁定是否安全?还是参考文献的变更会带来问题
  • 第二个示例中的代码执行速度会比第一个示例中的代码快吗
  • 有没有更好的方法来有效地解决上述问题
  • 有没有办法不用锁解决这个问题?(即使我认为这是不可能的)
  • 注意:这不是典型的生产者/消费者问题:消费者可以在生产者再次写入之前多次读取值-在生产者写入新数据之前,旧数据保持有效

    如第二个示例所示,锁定是否安全?还是参考文献的变更会带来问题

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

    即使这些值没有过时,您也可能会遇到比您希望的更多的争用。例如

    • 线程A调用
      writeValue()
    • 线程A在
      frontBuffer
      中锁定实例并开始复制
    • 线程B调用
      writeValue(int[])
    • 线程B写入数据,将当前锁定的
      frontBuffer
      实例移动到
      backBuffer
    • 线程B调用
      writeValue(int[])
    • 线程B等待
      backBuffer
      的锁,因为线程A仍然拥有它
    第二个示例中的代码执行速度会比第一个示例中的代码快吗

    我建议你对它进行分析并找出答案。只有当Y对你的特殊需求来说太慢时,X比Y快才重要,而你是唯一知道这些需求的人

    有没有更好的方法来有效地解决上述问题

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

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


    您可能能够,但我不建议您尝试,除非您非常熟悉多线程、缓存一致性和潜在的编译器/JIT优化。锁定很可能适合您的情况,您(以及其他阅读您的代码的人)可以更轻松地进行推理和维护。

    谢谢您的回答。首先,我向你保证,我的问题是假设性的。我知道正确锁定的好处,不想做坏事。然而,我对第二个示例中的代码不安全的说法感到好奇。你能告诉我,在代码中的什么地方,在什么情况下可能会发生不安全的操作吗?@Emiswelt哎呀,我看代码时脑子里有Friday,但看得不够仔细。我已经更新了我的答案,以解决我看到的问题。谢谢。我没想到你说的点心问题。