【问题标题】:How to avoid cached data in multithread application C++如何避免在多线程应用程序 C++ 中缓存数据
【发布时间】:2021-06-24 22:29:10
【问题描述】:

下一刻有人可以澄清一下。我看到了一些用于多线程目的的 std::queue 实现,其中推/弹出/擦除元素的所有操作都受互斥锁保护,但是当我看到我想象下一个场景时:我们有两个线程(线程 1 和线程 2),它们是在处理器的不同内核上运行,因此它们具有不同的 L1 缓存。我们有下一个队列:

struct Message {
    char* buf;
    size_t size;
};
std::queue<Message> messageQueue;

Thread1 将一些元素添加到队列中,然后 thread2 尝试使用 front() 方法访问一个元素,但是如果该内存块预先为处理器的这个核心缓存了怎么办(因此 size 变量可能不表示当前的大小buf 变量或 buf 指针可能持有错误(未更新)的地址)? 我在服务器端设计客户端/服务器应用程序时遇到了这样的问题。在我的应用程序服务器中运行在一个线程中,它直接与套接字一起工作,当它接收到新消息时,它为该消息分配内存并将该消息添加到某个消息队列中,然后其他线程访问该队列,处理消息然后删除它。我总是害怕缓存问题,因此我创建了自己的带有可变指针的队列实现。 处理这些事情的正确方法是什么?我必须避免使用带锁的 std::list、std::queue 吗?如果这个问题是不可能的,你能解释一下原因吗?

【问题讨论】:

  • “处理此类事情的正确方法”是使用互斥体,它将正确处理所有排序。 volatile 对象在这里没有任何用处。 volatile 的唯一含义是任何对它的引用都不会被优化。它不保证对对象的任何更改都会立即同步到所有其他执行线程。
  • 我确实相信 volatile 还可以防止缓存(变量每次使用时都会从内存中读取)。但我同意这不足以正确同步stackoverflow.com/questions/18695120/…
  • 但是如果我锁定了一些互斥体,我怎么能确定这个内存没有被缓存,或者互斥体锁定以某种方式保证直接从内存中读取?
  • C++ 标准要求在修改操作观察到由该修改操作写入的值(或可能在其间发生的一些其他操作)之后发生正确同步的读取操作。这是如何实现的,是一个内部实现细节。 CPU 提供允许编译器提供适当实现的机器指令(栅栏、内存屏障)。
  • 使用互斥锁比volaltile 需要花费数周时间调试的错误快得多。

标签: c++ multithreading caching


【解决方案1】:

这不是你的问题。您正在编写 C++ 代码。编译器的工作是确保你的代码让 CPU 和它的缓存做正确的事,而不是你的。您只需要遵守您使用的任何线程标准的规则。

但是如果我锁定了一些互斥体,我怎么能确定这个内存没有被缓存,或者互斥体锁定以某种方式保证直接从内存中读取?

你不能。这是一件好事。缓存大量提高了性能,而主内存非常慢了。幸运的是,您可能会在其上编写多线程代码的现代 CPU 不会要求您牺牲这样的性能。它们具有令人难以置信的复杂优化,例如缓存一致性硬件和预取固定,以避免严重损害性能的事情。你想要的是让你的代码工作,而不是让它工作得非常糟糕。

【讨论】:

    【解决方案2】:

    只要您在可能以线程方式访问/更新的任何地方都使用互斥锁,您就可以安全地使用它。这意味着对于这个示例,添加/删除/解析您的列表是互斥保护的。

    您还必须小心,您存储在队列中的对象受到适当保护。

    另外,请不要使用char *。这就是 std::string 的用途。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-02
      • 2014-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-31
      • 2020-01-22
      相关资源
      最近更新 更多