设置线程亲和性,通俗的说法就是将线程绑定到cpu上某一个或多个核上,此处的核是指逻辑核心,非物理核心。
物理核心与逻辑核心的关系,如果开启超线程,一般逻辑核心数=物理核心数*2。
一、SetThreadAffinityMask
微软帮助:
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadaffinitymask
函数定义如下:
DWORD_PTR SetThreadAffinityMask(HANDLE hThread,DWORD_PTR dwThreadAffinityMask);
参数含义:
hThread - 线程句柄。可以通过GetCurrentThread()获取当前线程的句柄。
dwThreadAffinityMask - 亲和性掩码。长度共8个字节,每一位代表线程绑定的一个cpu核。
比如,需要将线程绑定到
第0个核上:则mask=0×01
第1个核上:则mask=0×02
第2个核上:则mask=0×04 (注意不是0×03)
第3个核上:则mask=0×08,以此类推。
若需要绑定到多个核,比如
第0、1个:mask=0×03
第1、2个:mask=0×06,以此类推。
我们使用qt编写测试代码
Thread1.h如下:
#ifndef THREAD1_H
#define THREAD1_H
#include <QThread>
class Thread1 : public QThread
{
Q_OBJECT
protected:
virtual void run() override;
};
#endif // THREAD1_H
Thread1.cpp如下:
#include "Thread1.h"
#include <Windows.h>
void Thread1::run()
{
// 获取线程id
quint64 threadId = (quint64)QThread::currentThreadId();
// 线程id=>线程句柄
HANDLE handle = OpenThread(THREAD_ALL_ACCESS, false, threadId);
// 设置线程亲和性
SetThreadAffinityMask(handle, 0x02);
for (;;)
{
}
}
运行效果:
可以看到线程跑在第1个核上。
注意:
该函数第2个参数mask,只有8个字节大小,即8*8=64位,最多只能代表64个核。而某些服务器cpu核心数可能超过64,比如128核。
所以,在小于64核的系统上,可以使用此函数进行线程亲和性设置。
大于64核时,我们接下来看另外一个函数。
二、SetThreadGroupAffinity
微软帮助:
函数定义如下:
BOOL SetThreadGroupAffinity(
HANDLE hThread,
const GROUP_AFFINITY *GroupAffinity,
PGROUP_AFFINITY PreviousGroupAffinity
);
组亲和性结构如下:
typedef struct _GROUP_AFFINITY {
KAFFINITY Mask;
WORD Group;
WORD Reserved[3];
} GROUP_AFFINITY, *PGROUP_AFFINITY;
参数含义:
hThread - 线程句柄。可以通过GetCurrentThread()获取当前线程的句柄。
GroupAffinity - 处理器组亲和性结构。Mask表示处理器组中亲和性掩码;Group表示处理器组编号,从0开始;Reserved表示保留,一般为0。
关于处理器组的概念,可以参考:
阅读之后,对win下处理器分组方式有了了解。另外,由此函数中Group参数,可知,线程只能被绑定到一个处理器组内,而不能绑定多个处理器组,Mask就表示Group组内的一个或多个核。
如128核系统上,处理器组如下所示:
若线程需要绑定到处理器组0,则Group=0,
第0个核(Core0)上:则mask=0×01
第1个核(Core1)上:则mask=0×02
第2个核(Core2)上:则mask=0×04 (注意不是0×03)
第3个核(Core3)上:则mask=0×08,以此类推,与第一节中相同。
若线程需要绑定到处理器组1,则Group=1,
第0个核(Core64)上:则mask=0×01
第1个核(Core65)上:则mask=0×02
第2个核(Core66)上:则mask=0×04 (注意不是0×03)
第3个核(Core67)上:则mask=0×08,以此类推,与第一节中相同。
若需要绑定同个组内的多个核,与第一节中相同。
有时我们不关心处理器组编号,会使用拉通编号来表示处理器编号,如0-127核,而不是组0下0-63核,组1下64-127核。
比如需要将线程绑定到第64核,则需要先判断第64核属于哪个组,在组内排第几个。所以需要介绍2个函数,如下:
GetActiveProcessorGroupCount(),获取处理器组数量
GetActiveProcessorCount(WORD GroupNumber),获取某个处理器组下的处理器数量
由此2个函数可以编写一个由处理器编号获取所属组号的函数,如下:
int getGroupIndex(int processorIndex)
{
int count = processorIndex + 1;
WORD groupCount = GetActiveProcessorGroupCount();
for (WORD i = 0; i < groupCount; i++)
{
count = count - GetActiveProcessorCount(i);
if (count <= 0)
{
return i;
}
}
return -1; // processorIndex is invalid
}
我们使用qt编写测试代码
Thread2.h如下:
#ifndef THREAD2_H
#define THREAD2_H
#include <QThread>
class Thread2 : public QThread
{
Q_OBJECT
protected:
virtual void run() override;
};
#endif // THREAD2_H
Thread2.cpp如下:
#include "Thread2.h"
#include <Windows.h>
void Thread2::run()
{
// 获取线程id
quint64 threadId = (quint64)QThread::currentThreadId();
// 线程id=>线程句柄
HANDLE handle = OpenThread(THREAD_ALL_ACCESS, false, threadId);
// 处理器组亲和性结构
GROUP_AFFINITY group_affinity;
group_affinity.Reserved[0] = 0;
group_affinity.Reserved[1] = 0;
group_affinity.Reserved[2] = 0;
group_affinity.Mask = 0x04;
group_affinity.Group = 0x01;
// 设置线程亲和性
SetThreadGroupAffinity(handle, &group_affinity, nullptr);
for (;;)
{
}
}
运行效果:
可以看到线程跑在第66个核上。
三、总结
SetThreadAffinityMask函数,只能用于逻辑核心数<=64的系统。
SetThreadGroupAffinity函数,因为增加了处理器组编号参数,所以既可以用于<=64核系统,也可以用于>64核系统。
需要注意的是,线程只能与单个处理器组下面的逻辑处理器进行绑定,不能跨处理器组绑定,即单个线程只能在一个处理器组范围内,如线程1可以与处理器组0下1、2、3、4号处理器绑定,而不能与处理器组0下1号处理器,处理器组1下2号处理器同时进行绑定。进程与线程类似也是不能跨处理器组的,但是由于进程下可以有多个线程,故可以通过设置不同的线程绑定到不同的处理器组上,间接实现进程跨处理器组绑定。
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。
本文涉及工程代码,公众号回复:09SetThreadAffinityWin,即可下载。