Core Affinity 是我们可以在 .NET 中安全使用的东西吗?

Is Core Affinity Something that We Can Safely Do in .NET?

我在 Whosebug 中查看了为线程实现 Core Affinity 的信息,
在.NET 中。

一些答案说 .NET 不支持它自己的(托管)线程,
并且仅支持操作系统上的非托管线程运行。

另一方面,其他答案提到以下属性:
- ProcessThread.IdealProcessor link
- ProcessThread.ProcessorAffinity link

可以看出,这 2 个属性不是 Thread class 的属性,而是 ProcessThread class.

的属性

所以想请教一下:
如果有人正在创建 .NET 应用程序,
并希望为其应用程序的线程设置核心亲和力,
在 .NET 托管线程上这样做是否安全且受支持?

(如果是,那么我想知道为什么这 2 个属性会暴露在 ProcessThread class
而不是 Thread class?)

PS:我正在使用 .NET Framework v3.5 和 v2.0,
而不是较新版本的框架。

没有

.NET 线程不会将 1:1 映射到操作系统线程。线程关联适用于操作系统线程。由于运行时可以随意在 OS 个线程之间切换 .NET 线程,因此设置处理器亲缘关系充其量也无济于事,并且会在典型的多线程场景中浪费性能。

请注意,ProcessThread 仅包含有关进程中 运行 操作系统线程的信息。当您询问一个进程有哪些线程时,它就是您得到的结果——它是 OS 的一部分,而不是 .NET。相比之下,Thread 是关于 你的 个线程,而且只有托管线程。

这是一个更详细的答案,有些人可能会觉得有用。

如果您只想使用现有 .NET 标准的 API 来做到这一点,那么答案确实是 "no",我将解释原因。我还将讨论如何使用一组 .NET Standard 2.0 API 和单个 OS API 或单个 .NET Core 2.1 API 来实现这一点。

确实,一般来说,在托管应用程序的整个生命周期中,无法保证托管线程和本机线程之间固定的一对一映射。这基于 CLI 标准第 I.12.3.1 节:

The CLI manages multiple concurrent threads of control (not necessarily the same as the threads provided by a host operating system), multiple managed heaps, and a shared memory address space.

有一件事没有说得很清楚,就是用于运行托管线程的本机线程的集合是否都包含在同一个进程或多个进程中。但是 CLI 标准和 .NET 文档中的声明表明整个托管应用程序存在于单个 OS 进程中。此外,我不知道有任何在多个 OS 进程上安排托管线程的实现。

让我们首先考虑一个简单的情况,其中只有一个托管线程 and/or 只有一个本机线程。这种情况可以通过使用 Process.ProcessorAffinity 属性 设置整个进程的亲和力来轻松处理。无论单个托管线程如何映射到多个本机线程或单个本机线程如何映射到多个托管线程,整个托管应用程序只能有一个关联值。

否则,可以有多个亲和力。 ProcessThread 类型的本机线程提供只写 属性 ProcessorAffinity. The managed thread type Thread offers no such API. However, it offers the BeginThreadAffinity 静态方法,该方法允许当前托管线程将其映射固定到它当前映射到的任何本机线程,直到托管线程调用EndThreadAffinity。请注意 BeginThreadAffinity 并不是对 运行 时间的暗示。要么抛出异常,要么 returns 使用当前托管线程的固定映射成功。现在,如果我们可以获得当前本机线程,我们可以使用 ProcessThread.ProcessorAffinity 更改其处理器关联。不幸的是,与获取当前托管线程相比,没有标准的托管 API 即 returns 对当前本机线程的引用或标识符。如您所见,我们可以仅使用 .NET Standard 2.0 APIs 实现固定映射,但无法确定哪个本机线程映射到哪个托管线程。 我认为没有很好的技术理由为什么不使用这样的 API.

一种继续进行的方法是调用一些 OS 相关的 API 来获取当前本机线程的 ID。在 Windows 上,这是来自 kernel32.dllGetCurrentThreadId。当前托管线程可以调用这个API来获取当前本机线程的ID。然后,通过使用 Process.GetCurrentProcess().Threads 遍历本机线程并找到具有匹配 ID 的线程,可以获得对相应 ProcessThread 对象的引用。之后,可以使用ProcessThread.ProcessorAffinity来有效设置当前托管线程的亲和力。请注意,由于 ProcessThread.ProcessorAffinity 是只写的 属性,因此没有 .NET Standard 2.0 API 可以让您恢复旧的关联。不知道为什么是只写的

另一种更复杂的方法是使用 Thread.GetCurrentProcessorId,它目前仅存在于 .NET Core 2.1(最新版本)中。您可以将每个本机线程的亲和力设置为特定处理器,并检查哪个托管线程当前 运行正在该处理器上运行。最终,您可以确定哪个托管线程映射到哪个本机线程。

虽然可以实现上面讨论的固定映射,但在我看来 BeginThreadAffinity 并不能保证每个托管线程都映射到唯一的本机线程。因此,无法以符合标准的方式实现一对一映射。

在 .NET Core 中,ProcessThread.ProcessorAffinity 仅在 Windows 上实现。在其他 OS 上,它抛出一个 PlatformNotSupportedException