2个线程如何共享同一个缓存行

Jim*_*imm 12 c c++ linux multithreading

我正在使用自定义网络协议库.该库基于TCP/IP构建,据称可用于高频消息传递.它是一个非阻塞库,使用回调作为与调用者集成的接口.

我不是表演专家,这就是我决定在这里提出这个问题的原因.自定义库带有特定约束,概述如下:

"Callee不应该在回调线程的上下文中调用任何库的API.如果他们试图这样做,线程将挂起"

克服API限制的唯一方法是我启动另一个处理消息的线程并调用库来发送响应.库线程和进程线程将共享一个公共队列,该队列将受到互斥锁的保护,并使用wait_notify()调用来指示是否存在消息.

如果我每秒接收80k消息,那么我会让线程进入睡眠状态并经常唤醒它们,执行线程上下文切换每秒80k次.

另外,由于有两个线程,它们不会在L1缓存中共享消息缓冲区.包含消息的缓存行首先由库的线程填充,然后逐出并拉入进程线程的核心L1缓存.我是否遗漏了某些内容,或者图书馆的设计是否可能不适用于高性能用例?

我的问题是:

  1. 我已经看到过诸如"不要在回调的上下文中使用此API,因为它可能导致锁定"的警告.跨越许多图书馆.导致此类设计约束的常见设计选择是什么?如果是多次调用锁的同一线程的简单问题,他们可以使用递归锁.这是一个可重入的问题,以及哪些挑战可能导致API所有者制作不可重入的API?

  2. 在上面的设计模型中有没有办法,库线程和进程线程可以共享同一个内核,从而共享一个缓存行?

  3. volatile sig_atomic_t作为一种在两个线程之间共享数据的机制有多昂贵?

  4. 鉴于高频情况,在两个线程之间共享信息的轻量级方法是什么?

库和我的应用程序是基于C++和Linux构建的.

小智 6

2个线程如何共享同一个缓存行?

线程与缓存行无关.至少没有明确说明.可能出现的问题是在上下文切换和TLB失效时缓存刷新,但是对于线程给出相同的虚拟地址映射,缓存通常应该忽略这些事情.

导致此类设计约束的常见设计选择是什么?

图书馆的实施者不想处理:

  1. 复杂的锁定方案.
  2. 可重入逻辑(即你调用'send()',库调用你on_error(),你send()再次调用它- 这需要他们特别小心).

我个人认为,当涉及高性能,特别是与网络相关的事情时,围绕回调设计API是一件非常糟糕的事情.虽然有时它会让用户和开发人员的生活变得更加简单(仅仅是为了编写代码的简便性).唯一的例外可能是CPU中断处理,但这是一个不同的故事,你很难称之为API.

如果是多次调用锁的同一线程的简单问题,他们可以使用递归锁.

递归互斥体相对非常昂贵.关心运行时效率的人倾向于在可能的情况下避免使用它们.

在上面的设计模型中有没有办法,库线程和进程线程可以共享同一个内核,从而共享一个缓存行?

是.您必须将两个线程都固定到同一CPU内核.例如,通过使用sched_setaffinity().但这也超出了单个程序 - 整个环境必须正确配置.例如,您可能想要考虑不允许操作系统在该核心上运行任何东西,而是两个线程(包括中断),并且不允许这两个线程迁移到不同的CPU.

volatile sig_atomic_t作为在两个线程之间共享数据的机制有多昂贵?

它本身并不昂贵.但是,在多核环境中,您可能会缓存失效,停顿,增加的MESI流量等.鉴于两个线程都在同一个核心上并且没有任何入侵 - 唯一的惩罚是无法缓存变量,这是好的,因为它不应该被缓存(即编译器总是从内存中获取它,是缓存或主内存).

鉴于高频情况,在两个线程之间共享信息的轻量级方法是什么?

从/向同一内存读写.可能没有任何系统调用,阻塞调用等.例如,对于英特尔架构,至少可以通过使用内存屏障来实现两个并发线程的环形缓冲区.为了做到这一点,你必须非常注重细节.但是,如果某些东西必须明确同步,那么原子指令就是下一个层次.Haswell还带有Transactional Memory,可用于低开销同步.之后没有什么是快的.

另外,请参阅" 英特尔架构开发人员手册"第11章,了解内存缓存和控制.