查询线程(非进程)的处理器亲和性?

8
在Windows上,你可以为进程调用SetProcessAffinityMask函数,并为线程调用SetThreadAffinityMask函数。然而,Windows似乎仅公开了GetProcessAffinityMask而没有类似的API来处理进程中的单个线程。
我有一个多线程程序,在运行时将各个线程绑定到处理器。当我运行它时,我想(从外部)查询哪些线程在哪些处理器上运行,以确保它正常工作。我写了一个小型命令行实用程序来完成这个任务。但我似乎找不到一种方法来查找单个线程绑定的处理器或核心。
显然这是可能的;我在网上看到过adplus调试实用工具能够显示类似于pstack输出的线程亲和力信息。而且,在多处理器机器上,Process Explorer显示了一个“Threads”选项卡,显示了线程的“理想处理器”。
有人知道如何查询这个信息吗?
3个回答

10
你可以通过两个调用 SetThreadAffinityMask 来实现。该函数会返回传递的线程句柄的原始亲和性掩码。
因此... 使用一个将亲和性设置为一个 CPU 的掩码进行一次调用,然后进行第二次调用以恢复原始掩码。
这里是包括错误检查的完整 C/C++ 源代码:
DWORD GetThreadAffinityMask(HANDLE thread)
{
    DWORD mask = 1;
    DWORD old = 0;

    // try every CPU one by one until one works or none are left
    while(mask)
    {
        old = SetThreadAffinityMask(thread, mask);
        if(old)
        {   // this one worked
            SetThreadAffinityMask(thread, old); // restore original
            return old;
        }
        else
        {
            if(GetLastError() != ERROR_INVALID_PARAMETER)
                return 0; // fatal error, might as well throw an exception
        }
        mask <<= 1;
    }

    return 0;
}

这段代码逐个检查每个CPU,直到设置关联性成功(在这种情况下,我们现在知道原始掩码!)或者直到初始的1DWORD中移除为止。如果询问不可用的CPU,则该函数将失败并返回ERROR_INVALID_PARAMETER,然后我们将尝试下一个。通常第一个CPU会正常工作,因此效率相当高。
如果函数返回的结果不是ERROR_INVALID_PARAMETER,则意味着我们对句柄没有足够的访问权限,或者系统无法满足我们的请求而出现了一些真正的问题。因此,在这种情况下继续执行没有意义。

Damon,谢谢!这个方法非常好。还有一个后续问题:如果将其集成到任务列表式实用程序中,通过可能会对每个正在运行的进程的每个线程进行位操作来更改亲和性掩码,这会如何影响进程性能? - Dan
“正常”的情况是线程对所有CPU都有亲和力,包括第一个CPU。这种情况非常不寻常。因此,您通常不应该期望调用SetThreadAffinityMask超过2次。此外,我希望SetThreadAffinityMask的开销相当低,因为它可以通过TEB直接设置信息,甚至不需要上下文切换。因此,性能应该与GetLastError函数类似。我不希望调用它100次会有任何明显的影响(尽管只有分析才能确定!)。 - Damon
但是...你真的需要同时获取系统上所有线程的亲和力吗?大多数类似于tasklist的程序一次只查询一个线程的亲和力,如果你特别要求的话。此外,如果这真的是个问题,你仍然可以在一个工作线程中查询所有线程的亲和力,调用SwitchToThread或Sleep(0),然后查询下一个...并为每个更新向GUI线程发送消息。这将非常快速地填充整个列表,并且没有任何影响。 - Damon

7
使用ThreadBasicInformation调用NtQueryInformationThread函数:
typedef struct _THREAD_BASIC_INFORMATION
{
    NTSTATUS ExitStatus;
    PTEB TebBaseAddress;
    CLIENT_ID ClientId;
    ULONG_PTR AffinityMask;
    KPRIORITY Priority;
    LONG BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

据我所知,目前没有记录获取线程亲和性的方法。

0
更快的方法是调用GetCurrentProcessorNumber,请参见msdn,该函数将返回当前线程在调用此函数时运行的处理器编号。
C# 代码:
/// <summary>
/// Retrieves the number of the processor the current thread was running on <para/>
/// during the call to this function.
/// </summary>
/// <returns>The function returns the current processor number.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int GetCurrentProcessorNumber();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接