我正在使用自定义网络协议(protocol)库。这个库建立在 TCP/IP 之上,应该用于高频消息传递。它是一个非阻塞库,使用回调作为接口(interface)与调用者集成。
我不是性能专家,这就是我决定在这里提出这个问题的原因。自定义库带有一个特定的约束,概述如下:
"Callee should not invoke any of the library's API under the context of the callback thread. If they attempt to do so, the thread will hang"
克服 API 限制的唯一方法是我启动另一个线程来处理消息并调用库以发送响应。库线程和进程线程将共享一个公共(public)队列,该队列受互斥体保护并使用 wait_notify()
调用来指示消息的存在。
如果我每秒接收 80k 条消息,那么我会让线程休眠并经常唤醒它们,每秒执行大约 80k 次线程上下文切换。
另外,由于有两个线程,它们不会共享 L1 缓存中的消息缓冲区。包含消息的缓存行将首先由库的线程填充,然后被驱逐并拉入进程线程的核心的 L1 缓存。我是否遗漏了什么,或者该库的设计可能不适用于高性能用例?
我的问题是:
我看到过类似“不要在回调的上下文中使用此 API,因为它会导致锁定”之类的警告。跨越许 multimap 书馆。导致此类设计约束的常见设计选择是什么?如果这是同一个线程多次调用锁的简单问题,他们可以使用递归锁。这是一个可重入问题吗?哪些挑战可能导致 API 所有者制作不可重入 API?
在上述设计模型中,有没有办法让库线程和进程线程共享同一个内核,从而共享一个缓存线?
volatile
sig_atomic_t
作为一种在两个线程之间共享数据的机制有多贵?给定一个高频场景,在两个线程之间共享信息的轻量级方法是什么?
该库和我的应用程序是基于 C++ 和 Linux 构建的。
最佳答案
How can 2 threads share the same cache line?
线程与缓存行无关。至少不是明确的。可能出问题的是上下文切换和 TLB 失效时的缓存刷新,但考虑到线程的相同虚拟地址映射,缓存通常应该忽略这些事情。
What are the common design choices that cause such design constraints?
库的实现者不想处理:
- 复杂的锁定方案。
- 重入逻辑(即你调用'send()',库用
on_error()
回调你,你再次调用send()
- 那他们需要特别小心)。
我个人认为,在涉及高性能,尤其是与网络相关的事情时,围绕回调设计 API 是一件非常糟糕的事情。尽管有时它使用户和开发人员的生活变得更加简单(仅就编写代码而言)。唯一的异常(exception)可能是 CPU 中断处理,但那是另一回事,您很难将其称为 API。
They can use recursive locks if it is a simple question of same thread calling the lock multiple times.
递归互斥锁相对来说非常昂贵。关心运行时效率的人往往会尽可能避免使用它们。
Is there a way in the above design model, where the library thread and process thread can share the same core, and consequently share a cache line?
是的。您必须将两个线程固定到同一个 CPU 内核。例如,使用 sched_setaffinity()
.但这也超出了单个程序的范围——必须正确配置整个环境。例如,您可能要考虑不允许操作系统在该内核上运行任何东西,但您的两个线程(包括中断),并且不允许这两个线程迁移到不同的 CPU。
How expensive are volatile sig_atomic_t's as a mechanism to share data between two threads?
它本身并不昂贵。然而,在多核环境中,您可能会出现一些缓存失效、停顿、MESI 流量增加等问题。鉴于两个线程都在同一个核心上并且没有任何干扰——唯一的惩罚是无法缓存变量,这是可以的,因为它不应该被缓存(即编译器总是从内存中获取它,无论是缓存还是主内存)。
Given a high frequency scenario, what is a light-weight way to share information between two threads?
从/向同一内存读取和写入。可能没有任何系统调用、阻塞调用等。例如,至少对于英特尔架构,可以通过使用内存屏障来实现两个并发线程的环形缓冲区,仅此而已。为了做到这一点,你必须非常注重细节。然而,如果某些东西必须显式同步,那么原子指令是下一个层次。 Haswell 还带有可用于低开销同步的事务内存。在那之后没有什么是快速的。
另外,看看 Intel Architectures Developer's Manual, Chapter 11 ,关于内存缓存和控制。
关于c++ - 2个线程如何共享同一个缓存行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14371695/