我应该相信 std::thread 不是通过仅通过创建用户线程来实现的



我了解到,如果其中一个线程调用某些系统调用(如 I/O 系统调用),则与内核线程映射的所有用户线程都会被阻塞。

如果std::thread是通过在某些环境中仅创建用户线程来实现的,则某些程序中用于 I/O 的线程可能会阻塞用于渲染的线程。

所以我认为区分用户/内核很重要,但 c++ 标准并不重要。

那么我如何确保上述某些情况不会在特定环境(如 Windows10)中发生?

我了解到,如果其中一个线程调用某些系统调用(如 I/O 系统调用),则与内核线程映射的所有用户线程都会被阻止。

是的,但是很少有任何东西直接使用内核的系统调用。通常,它们使用用户空间库。对于通常阻塞的"系统"调用(例如标准 C 库中的read()函数),库可以使用异步函数(例如标准 C 库中的aio_read()函数)和用户空间线程切换来模拟它。

所以我认为区分用户/内核很重要,但 c++ 标准并不重要。

这很重要,但原因不同。

用户空间线程的第一个问题是内核不知道线程优先级。如果您想象一台计算机运行 2 个完全独立的应用程序(用户使用"alt+tab"在它们之间切换),其中每个应用程序都有一个高优先级线程(用于用户界面),很少有中等优先级线程(用于一般工作)加上一些低优先级线程(用于在后台执行预取和预计算等操作);您最终可能会遇到这样一种情况:内核将 CPU 时间提供给一个应用程序(将 CPU 时间用于低优先级线程),因为它不知道另一个应用程序需要 CPU 时间来处理其高优先级线程。

换句话说,对于多进程环境,用户空间线程处理具有很高的风险,即浪费 CPU 时间执行不相关的工作(在一个进程中),而重要的工作(在另一个进程中)等待。

用户空间线程的第二个问题是(对于现代系统)良好的调度决策会考虑不同 CPU 之间的差异("大。很少",超线程,哪些缓存由哪些 CPU 共享,..)和电源管理(例如,对于低优先级线程,降低 CPU 时钟速度以延长电池寿命和/或降低 CPU 温度是合理的,以便在以后需要完成更高优先级的工作时它们可以更快地运行更长时间);用户空间没有所需的信息(也没有改变CPU速度等的能力),也无法做出良好的调度决策。

请注意,这些问题可以通过在用户空间和内核之间进行大量通信来"修复"(用户空间线程通知内核等待线程和当前正在运行的线程的线程优先级,内核通知用户空间CPU差异和电源管理等);但是用户空间线程切换的全部意义在于避免内核系统调用的成本, 因此,用户空间和内核之间的这种通信将使用户空间线程切换毫无意义。

那么我如何确保上述某些情况不会在特定环境(如 Windows10)中发生?

你不能。这不是你的决定。

当你选择使用高级抽象(std::threadC++而不是直接从汇编语言使用内核)时,你故意将低级决策委托给其他东西(编译器及其运行时环境)。优点(您不再需要关心这些决定)是缺点(您不再能够做出这些决定)。

在与 OP 交谈并更好地理解真正被问到的问题之后,重新措辞我试图回答的问题。

大多数 I/O 操作都是按线程级别阻塞的:如果一个线程启动一个线程,则只会阻塞此线程,而不是整个进程。

OP 似乎打算在线程中启动渲染操作,并且不希望它被该线程中的 I/O 操作阻止。两种可能的解决方案是:

  1. 生成另一个线程以执行此阻塞 I/O 操作,然后让呈现线程独立于 I/O 继续;
  2. 若要使用特定于每个操作系统的资源(不属于C++),请以异步、非阻塞形式启动相同的 I/O 操作。
  3. 最后,为了最大程度地减少对操作系统对 I/O 的访问,应用程序开发人员可以做的是尝试确保不会同时访问同一 I/O 设备。

你可以放心,std::thread没有使用"用户线程",因为这个概念在世纪之交几乎已经消亡了。

现代硬件具有多个 CPU 内核,如果有足够的内核线程,这些内核可以更好地工作。如果内核线程不足,CPU 内核可能会处于空闲状态。

"用户线程"的想法起源于一个只有一个CPU内核的时代,人们反而担心内核线程太多。

最新更新