openGauss-server/src/gausskernel/process/threadpool/threadpool_controler.cpp

957 lines
34 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* -------------------------------------------------------------------------
*
* threadpool_controler.cpp
* 线程池的控制器。定义了 ThreadPoolControler 类,
* 用于初始化线程池的工作线程和监听线程,并将新会话从
* 主线程分派到合适的线程组。
*
*
* IDENTIFICATION
* src/gausskernel/process/threadpool/threadpool_controler.cpp
*
* ---------------------------------------------------------------------------------------
*/
#ifdef __USE_NUMA
#include <numa.h>
#endif
#include "postgres.h"
#include "knl/knl_variable.h"
#include "threadpool/threadpool.h"
#include "access/xact.h"
#include "catalog/pg_collation.h"
#include "commands/copy.h"
#include "gssignal/gs_signal.h"
#include "lib/dllist.h"
#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "pgxc/pgxc.h"
#include "storage/pmsignal.h"
#include "tcop/dest.h"
#include "utils/atomic.h"
#include "utils/builtins.h"
#include "utils/formatting.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/palloc.h"
#include "utils/ps_status.h"
#include "executor/executor.h"
#include "communication/commproxy_interface.h"
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
ThreadPoolControler* g_threadPoolControler = NULL;
#define BUFSIZE 128
#define IS_NULL_STR(str) ((str) == NULL || (str)[0] == '\0')
#define INVALID_ATTR_ERROR(detail) \
ereport(FATAL, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid attribute for thread pool."), detail))
static const long one_hundred_micro_sec = 100;
// 线程池控制器的构造函数
ThreadPoolControler::ThreadPoolControler()
{
m_threadPoolContext = NULL; // 线程池上下文
m_sessCtrl = NULL; // 会话控制
m_groups = NULL; // 线程组
m_scheduler = NULL; // 调度器
m_groupNum = 1; // 线程组数量
m_threadNum = 0; // 线程数量
m_maxPoolSize = 0; // 最大线程池大小
m_maxStreamPoolSize = 0; // 最大流线程池大小
m_streamProcRatio = 0; // 流处理比例
}
ThreadPoolControler::~ThreadPoolControler()
{
delete m_scheduler; // 删除调度器对象
delete m_sessCtrl; // 删除会话控制对象
MemoryContextDelete(m_threadPoolContext); // 删除线程池上下文
m_threadPoolContext = NULL; // 将线程池上下文置为空
m_groups = NULL; // 将线程组指针置为空
m_sessCtrl = NULL; // 将会话控制指针置为空
}
void ThreadPoolControler::Init(bool enableNumaDistribute)
{
// 创建线程池上下文
m_threadPoolContext = AllocSetContextCreate(g_instance.instance_context,
"ThreadPoolContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE,
SHARED_CONTEXT);
// 切换到线程池上下文
AutoContextSwitch memSwitch(m_threadPoolContext);
// 分配线程组数组
m_groups = (ThreadPoolGroup**)palloc(sizeof(ThreadPoolGroup*) * m_groupNum);
// 创建线程池会话控制对象
m_sessCtrl = New(CurrentMemoryContext) ThreadPoolSessControl(CurrentMemoryContext);
// 检查是否绑定 CPU
bool bindCpu = CheckCpuBind();
// 检查是否绑定 CPU NUMA
bool bindCpuNuma = CheckCpuNumaBind();
int maxThreadNum = 0;
int expectThreadNum = 0;
int maxStreamNum = 0;
int numaId = 0;
int cpuNum = 0;
int *cpuArr = NULL;
for (int i = 0; i < m_groupNum; i++) {
if (bindCpu) {
while (m_cpuInfo.cpuArrSize[numaId] == 0)
numaId++;
#ifdef __USE_NUMA
// 如果启用 NUMA 分配,则设置首选 NUMA 节点
if (enableNumaDistribute) {
numa_set_preferred(numaId);
}
#endif
// 获取当前 NUMA 节点的 CPU 数量
Assert(numaId < m_cpuInfo.totalNumaNum);
expectThreadNum = (int)round(
(double)m_threadNum * ((double)m_cpuInfo.cpuArrSize[numaId] / (double)m_cpuInfo.activeCpuNum));
maxThreadNum = (int)round(
(double)m_maxPoolSize * ((double)m_cpuInfo.cpuArrSize[numaId] / (double)m_cpuInfo.activeCpuNum));
maxStreamNum = (int)round(
(double)m_maxPoolSize * ((double)m_cpuInfo.cpuArrSize[numaId] / (double)m_cpuInfo.activeCpuNum));
cpuNum = m_cpuInfo.cpuArrSize[numaId];
cpuArr = m_cpuInfo.cpuArr[numaId];
// 增加 NUMA ID以处理下一个 NUMA 节点
numaId++;
} else {
// 如果未绑定 CPU则均匀分配线程和流的数量
expectThreadNum = m_threadNum / m_groupNum;
maxThreadNum = m_maxPoolSize / m_groupNum;
maxStreamNum = m_maxPoolSize / m_groupNum;
numaId = -1;
}
// 创建线程池组对象并初始化
m_groups[i] = New(CurrentMemoryContext)ThreadPoolGroup(maxThreadNum, expectThreadNum,
maxStreamNum, i, numaId, cpuNum, cpuArr, bindCpuNuma);
m_groups[i]->Init(enableNumaDistribute);
}
// 等待所有线程池组准备就绪
for (int i = 0; i < m_groupNum; i++) {
m_groups[i]->WaitReady();
}
#ifdef __USE_NUMA
if (enableNumaDistribute) {
/* 设置为交织模式以供除工作线程之外的其他线程使用 */
numa_set_interleave_mask(numa_all_nodes_ptr);
}
#endif
// 创建线程池调度器对象并启动
m_scheduler = New(CurrentMemoryContext) ThreadPoolScheduler(m_groupNum, m_groups);
m_scheduler->StartUp();
}
void ThreadPoolControler::SetThreadPoolInfo()
{
InitCpuInfo();
GetInstanceBind(&m_cpuset);
GetCpuAndNumaNum(&m_cpuInfo.totalCpuNum, &m_cpuInfo.totalNumaNum);
ParseAttr();
ParseStreamAttr();
GetSysCpuInfo();
SetGroupAndThreadNum();
SetStreamInfo();
}
void AdjustThreadAffinity(void) {
cpu_set_t m_cpuset;
CPU_ZERO(&m_cpuset);
// 检查实例是否已绑定到某些特定的 CPU
int ret = pthread_getaffinity_np(PostmasterPid, sizeof(cpu_set_t), &m_cpuset);
if (ret == 0) {
// 如果绑定到 CPU 0 而未绑定到 CPU 1则重新设置 CPU 亲和性,以避免绑定到单一 CPU
if ((CPU_ISSET(0, &m_cpuset)) && (!CPU_ISSET(1, &m_cpuset))) {
int num_processors = sysconf(_SC_NPROCESSORS_CONF);
CPU_ZERO(&m_cpuset);
for (int j = 0; j < num_processors; j++) {
CPU_SET(j, &m_cpuset);
}
// 设置线程的 CPU 亲和性
int s = pthread_setaffinity_np(PostmasterPid, sizeof(cpu_set_t), &m_cpuset);
if (s != 0) {
ereport(WARNING, (errmsg("AdjustThreadAffinity fail to bind thread %lu, errno: %d", PostmasterPid, ret)));
}
}
}
}
void ThreadPoolControler::GetInstanceBind(cpu_set_t *cpuset) {
// 修复 libgomp 在某些指定操作系统上的 bug
AdjustThreadAffinity();
// 检查实例是否已绑定到某些特定的 CPU
int ret = pthread_getaffinity_np(PostmasterPid, sizeof(cpu_set_t), cpuset);
if (ret == 0) {
return;
} else {
// 初始化 cpuset以避免未绑定的情况
errno_t rc = memset_s(cpuset, sizeof(cpu_set_t), 0, sizeof(cpu_set_t));
securec_check(rc, "\0", "\0");
}
}
void ThreadPoolControler::ParseAttr()
{
m_attr.threadNum = DEFAULT_THREAD_POOL_SIZE;
m_attr.groupNum = DEFAULT_THREAD_POOL_GROUPS;
m_attr.bindCpu = NULL;
/* 复制字符串并移除空格 */
char* attr = TrimStr(g_instance.attr.attr_common.thread_pool_attr);
if (IS_NULL_STR(attr))
return;
char* ptoken = NULL;
char* psave = NULL;
const char* pdelimiter = ",";
/* 获取线程数 */
ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave));
if (!IS_NULL_STR(ptoken))
m_attr.threadNum = pg_strtoint32(ptoken);
pfree_ext(ptoken);
/* 获取线程组数 */
ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave));
if (!IS_NULL_STR(ptoken))
m_attr.groupNum = pg_strtoint32(ptoken);
pfree_ext(ptoken);
if (m_attr.threadNum < 0 || m_attr.threadNum > MAX_THREAD_POOL_SIZE)
INVALID_ATTR_ERROR(
errdetail("当前线程数 %d 超出范围 [%d, %d]。", m_attr.threadNum, 0, MAX_THREAD_POOL_SIZE));
if (m_attr.groupNum < 0 || m_attr.groupNum > MAX_THREAD_POOL_GROUPS)
INVALID_ATTR_ERROR(
errdetail("当前线程组数 %d 超出范围 [%d, %d]。", m_attr.groupNum, 0, MAX_THREAD_POOL_GROUPS));
/* 获取绑定 CPU */
m_attr.bindCpu = TrimStr(psave);
ParseBindCpu();
pfree_ext(attr);
}
void ThreadPoolControler::ParseStreamAttr()
{
m_stream_attr.threadNum = DEFAULT_THREAD_POOL_SIZE;
m_stream_attr.procRatio = DEFAULT_THREAD_POOL_STREAM_PROC_RATIO;
m_stream_attr.groupNum = DEFAULT_THREAD_POOL_GROUPS;
m_stream_attr.bindCpu = NULL;
// 获取流式线程池属性配置字符串
char* attr = TrimStr(g_instance.attr.attr_common.thread_pool_stream_attr);
if (IS_NULL_STR(attr)) {
return;
}
char* ptoken = NULL;
char* psave = NULL;
const char* pdelimiter = ",";
// 解析流式线程池的最大线程数
ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave));
if (IS_NULL_STR(ptoken) || !isdigit((unsigned char)*ptoken)) {
INVALID_ATTR_ERROR(
errdetail("当前 thread_pool_stream_attr 格式错误stream_thread_num 必须为数字。"));
}
m_stream_attr.threadNum = pg_strtoint32(ptoken);
pfree_ext(ptoken);
if (m_stream_attr.threadNum < 0 || m_stream_attr.threadNum > MAX_THREAD_POOL_SIZE) {
INVALID_ATTR_ERROR(
errdetail("当前 stream_thread_num %d 超出范围 [%d, %d]。",
m_stream_attr.threadNum, 0, MAX_THREAD_POOL_SIZE));
}
// 解析流式线程池的处理比例
ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave));
if (IS_NULL_STR(ptoken) || !isdigit((unsigned char)*ptoken)) {
INVALID_ATTR_ERROR(
errdetail("当前 thread_pool_stream_attr 格式错误stream_proc_ratio 必须为数字。"));
}
m_stream_attr.procRatio = atof(ptoken);
pfree_ext(ptoken);
if (m_stream_attr.procRatio <= 0 || m_stream_attr.procRatio > MAX_THREAD_POOL_STREAM_PROC_RATIO) {
INVALID_ATTR_ERROR(
errdetail("当前 stream_proc_ratio %f 超出范围 (%d, %d]。",
m_stream_attr.procRatio, 0, MAX_THREAD_POOL_STREAM_PROC_RATIO));
}
pfree_ext(attr);
return;
}
void ThreadPoolControler::ParseBindCpu()
{
if (IS_NULL_STR(m_attr.bindCpu)) {
m_cpuInfo.bindType = NO_CPU_BIND;
return;
}
// 复制属性字符串以进行解析
char* pattr = pstrdup(m_attr.bindCpu);
char* scpu = pattr;
char* ptoken = NULL;
char* psave = NULL;
const char* pdelimiter = ":";
int bindNum = 0;
// 检查属性字符串的格式是否正确
if (scpu[0] != '(' || scpu[strlen(scpu) - 1] != ')')
INVALID_ATTR_ERROR("使用 '(' ')' 来表示 CPU 绑定信息。");
scpu++;
scpu[strlen(scpu) - 1] = '\0';
// 解析属性字符串并转换为小写
ptoken = TrimStr(strtok_r(scpu, pdelimiter, &psave));
ptoken = pg_strtolower(ptoken);
// 根据不同的属性类型进行解析
if (strncmp("nobind", ptoken, strlen("nobind")) == 0) {
m_cpuInfo.bindType = NO_CPU_BIND;
return;
} else if (strncmp("allbind", ptoken, strlen("allbind")) == 0) {
m_cpuInfo.bindType = ALL_CPU_BIND;
return;
} else if (strncmp("cpubind", ptoken, strlen("cpubind")) == 0) {
m_cpuInfo.bindType = CPU_BIND;
m_cpuInfo.isBindCpuArr = (bool*)palloc0(sizeof(bool) * m_cpuInfo.totalCpuNum);
bindNum = ParseRangeStr(psave, m_cpuInfo.isBindCpuArr, m_cpuInfo.totalCpuNum, "cpubind");
} else if (strncmp("nodebind", ptoken, strlen("nodebind")) == 0) {
m_cpuInfo.bindType = NODE_BIND;
m_cpuInfo.isBindNumaArr = (bool*)palloc0(sizeof(bool) * m_cpuInfo.totalNumaNum);
bindNum = ParseRangeStr(psave, m_cpuInfo.isBindNumaArr, m_cpuInfo.totalNumaNum, "nodebind");
} else if (strncmp("numabind", ptoken, strlen("numabind")) == 0) {
m_cpuInfo.bindType = NUMA_BIND;
m_cpuInfo.isBindCpuNumaArr = (bool*)palloc0(sizeof(bool) * m_cpuInfo.totalCpuNum);
bindNum = ParseRangeStr(psave, m_cpuInfo.isBindCpuNumaArr, m_cpuInfo.totalCpuNum, "numabind");
} else {
INVALID_ATTR_ERROR(errdetail("只有 'nobind', 'allbind', 'cpubind', 'nodebind' 和 'numabind' "
"是有效的属性。"));
}
// 检查是否找到有效的 CPU 进行线程绑定
if (bindNum == 0)
INVALID_ATTR_ERROR(
errdetail("无法找到有效的 CPU 进行线程绑定,可能的原因有两个:\n"
"1. 这些 CPU 不处于活动状态,请使用 lscpu 命令检查在线 CPU 列表。\n"
"2. 进程已绑定到其他 CPU且没有交集请使用 taskset -pc 命令检查进程 CPU 绑定信息。\n"));
pfree_ext(ptoken);
pfree_ext(pattr);
}
int ThreadPoolControler::ParseRangeStr(char* attr, bool* arr, int totalNum, char* bindtype)
{
char* ptoken = NULL;
char* psave = NULL;
const char* pdelimiter = ",";
int retNum = 0;
// 解析属性字符串
ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave));
while (!IS_NULL_STR(ptoken)) {
char* pt = NULL;
char* ps = NULL;
const char* pd = "-";
int startid = -1;
int endid = -1;
// 解析范围字符串,可能包含起始和结束值
pt = TrimStr(strtok_r(ptoken, pd, &ps));
if (!IS_NULL_STR(pt))
startid = pg_strtoint32(pt);
if (!IS_NULL_STR(ps))
endid = pg_strtoint32(ps);
// 检查解析的值是否有效
if (startid < 0 && endid < 0)
INVALID_ATTR_ERROR(errdetail("无法解析属性 %s", pt));
if (startid >= totalNum)
INVALID_ATTR_ERROR(
errdetail("属性 %s 中的 %d 超出了有效范围 [%d, %d]", bindtype, startid, 0, totalNum - 1));
if (endid >= totalNum)
INVALID_ATTR_ERROR(
errdetail("属性 %s 中的 %d 超出了有效范围 [%d, %d]", bindtype, endid, 0, totalNum - 1));
if (endid == -1) {
// 单个 CPU 绑定
retNum += arr[startid] ? 0 : 1;
arr[startid] = true;
} else {
// 范围内的多个 CPU 绑定
if (startid > endid) {
int tmpid = startid;
startid = endid;
endid = tmpid;
}
for (int i = startid; i <= endid; i++) {
retNum += arr[i] ? 0 : 1;
arr[i] = true;
}
}
// 释放临时字符串内存,不需要在错误发生时释放,这里的错误是致命的!
pfree_ext(pt);
pfree_ext(ptoken);
ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave));
}
return retNum;
}
bool* ThreadPoolControler::GetMcsCpuInfo(int totalCpuNum)
{
FILE* fp = NULL;
char buf[BUFSIZE];
bool* isMcsCpuArr = (bool*)palloc0(sizeof(bool) * totalCpuNum);
/*
* 当数据库部署在MCS上时我们需要读取cpuset.cpus文件以查找此MCS中可用的CPU。
* 如果我们能够读取此文件那么我们认为所有CPU都是可用的。
*/
fp = fopen("/sys/fs/cgroup/cpuset/cpuset.cpus", "r");
if (fp == NULL) {
ereport(WARNING, (errcode(ERRCODE_OPERATE_INVALID_PARAM),
errmsg("Failed to open file /sys/fs/cgroup/cpuset/cpuset.cpus")));
errno_t rc = memset_s(isMcsCpuArr, totalCpuNum, 1, totalCpuNum);
securec_check(rc, "", "");
return isMcsCpuArr;
}
if (fgets(buf, BUFSIZE, fp) == NULL) {
ereport(WARNING, (errcode(ERRCODE_OPERATE_INVALID_PARAM),
errmsg("Failed to read file /sys/fs/cgroup/cpuset/cpuset.cpus")));
errno_t rc = memset_s(isMcsCpuArr, totalCpuNum, 1, totalCpuNum);
securec_check(rc, "", "");
fclose(fp);
return isMcsCpuArr;
}
int mcsNum = ParseRangeStr(buf, isMcsCpuArr, totalCpuNum, "Mcs Cpu set");
if (mcsNum == 0) {
ereport(WARNING, (errcode(ERRCODE_OPERATE_INVALID_PARAM),
errmsg("No available CPUs in /sys/fs/cgroup/cpuset/cpuset.cpus")));
errno_t rc = memset_s(isMcsCpuArr, totalCpuNum, 1, totalCpuNum);
securec_check(rc, "", "");
}
fclose(fp);
return isMcsCpuArr;
}
void ThreadPoolControler::GetActiveCpu(NumaCpuId *numaCpuIdList, int *num)
{
*num = 0; // 初始化传出参数 num 为 0
char buf[BUFSIZE];
FILE* fp = popen("lscpu -b -e=cpu,node", "r"); // 打开一个用于读取 CPU 信息的流
if (fp == NULL) {
ereport(WARNING, (errmsg("Unable to use 'lscpu' to read CPU info."))); // 如果打开流失败,发出警告消息
return;
}
char* ptoken = NULL;
char* psave = NULL;
const char* pdelimiter = " ";
int cpuid = 0;
int numaid = 0;
// 尝试读取头部信息
if (fgets(buf, sizeof(buf), fp) != NULL) {
while (fgets(buf, sizeof(buf), fp) != NULL) {
ptoken = strtok_r(buf, pdelimiter, &psave);
if (!IS_NULL_STR(ptoken)) {
cpuid = pg_strtoint32(ptoken); // 解析 CPU ID
}
ptoken = strtok_r(NULL, pdelimiter, &psave);
if (!IS_NULL_STR(ptoken)) {
numaid = pg_strtoint32(ptoken); // 解析 NUMA ID
}
numaCpuIdList[*num].cpuId = cpuid; // 将 CPU ID 存入传出参数
numaCpuIdList[*num].numaId = numaid; // 将 NUMA ID 存入传出参数
(*num)++; // 递增传出参数 num
}
}
pclose(fp); // 关闭流
}
void ThreadPoolControler::GetSysCpuInfo()
{
if (m_cpuInfo.totalNumaNum == 0 || m_cpuInfo.totalCpuNum == 0) {
ereport(WARNING, (errmsg("Fail to read cpu num or numa num."))); // 如果没有读取到 CPU 数量或 NUMA 数量,发出警告消息
return;
}
m_cpuInfo.isMcsCpuArr = GetMcsCpuInfo(m_cpuInfo.totalCpuNum); // 获取 MCS CPU 信息
m_cpuInfo.cpuArr = (int**)palloc0(sizeof(int*) * m_cpuInfo.totalNumaNum);
m_cpuInfo.cpuArrSize = (int*)palloc0(sizeof(int) * m_cpuInfo.totalNumaNum);
int cpu_per_numa = m_cpuInfo.totalCpuNum / m_cpuInfo.totalNumaNum;
for (int i = 0; i < m_cpuInfo.totalNumaNum; i++) {
m_cpuInfo.cpuArr[i] = (int*)palloc0(sizeof(int) * cpu_per_numa); // 为每个 NUMA 节点分配内存
}
m_cpuInfo.activeCpuNum = 0;
NumaCpuId *sysNumaCpuIdList = (NumaCpuId*)palloc0(sizeof(NumaCpuId) * m_cpuInfo.totalCpuNum);
int sysNumaCpuIdNum = 0;
GetActiveCpu(sysNumaCpuIdList, &sysNumaCpuIdNum); // 获取激活的 CPU 信息
if (sysNumaCpuIdNum == 0) {
return;
}
for (int i = 0; i < sysNumaCpuIdNum; ++i) {
int cpuid = sysNumaCpuIdList[i].cpuId;
int numaid = sysNumaCpuIdList[i].numaId;
if (IsActiveCpu(cpuid, numaid)) {
m_cpuInfo.cpuArr[numaid][m_cpuInfo.cpuArrSize[numaid]] = cpuid; // 将 CPU ID 存入相应 NUMA 节点的数组中
m_cpuInfo.cpuArrSize[numaid]++; // 递增相应 NUMA 节点的数组大小
m_cpuInfo.activeCpuNum++; // 递增激活的 CPU 数量
}
}
pfree_ext(sysNumaCpuIdList); // 释放内存
for (int i = 0; i < m_cpuInfo.totalNumaNum; i++) {
if (m_cpuInfo.cpuArrSize[i] > 0)
m_cpuInfo.activeNumaNum++; // 统计激活的 NUMA 节点数量
}
}
void ThreadPoolControler::InitCpuInfo()
{
m_cpuInfo.totalCpuNum = 0; // 初始化总 CPU 数量为 0
m_cpuInfo.activeCpuNum = 0; // 初始化激活的 CPU 数量为 0
m_cpuInfo.totalNumaNum = 0; // 初始化总 NUMA 节点数量为 0
m_cpuInfo.activeNumaNum = 0; // 初始化激活的 NUMA 节点数量为 0
m_cpuInfo.cpuArrSize = NULL; // 初始化 CPU 数组大小为 NULL
m_cpuInfo.cpuArr = NULL; // 初始化 CPU 数组为 NULL
m_cpuInfo.bindType = NO_CPU_BIND; // 初始化 CPU 绑定类型为 NO_CPU_BIND
m_cpuInfo.isBindCpuArr = NULL; // 初始化 CPU 绑定数组为 NULL
m_cpuInfo.isBindNumaArr = NULL; // 初始化 NUMA 节点绑定数组为 NULL
m_cpuInfo.isMcsCpuArr = NULL; // 初始化 MCS CPU 数组为 NULL
}
void ThreadPoolControler::GetCpuAndNumaNum(int32 *totalCpuNum, int32 *totalNumaNum)
{
char buf[BUFSIZE];
FILE* fp = NULL;
// 打开 "lscpu" 命令的输出以获取 CPU 和 NUMA 节点数量信息
if ((fp = popen("LANG=en_US.UTF-8;lscpu", "r")) != NULL) {
while (fgets(buf, sizeof(buf), fp) != NULL) {
if (strncmp("CPU(s)", buf, strlen("CPU(s)")) == 0 &&
strncmp("On-line CPU(s) list", buf, strlen("On-line CPU(s) list")) != 0 &&
strncmp("NUMA node", buf, strlen("NUMA node")) != 0) {
// 当遇到包含 "CPU(s)" 的行时,解析并获取总 CPU 数量
char* loc = strchr(buf, ':');
*totalCpuNum = pg_strtoint32(loc + 1);
} else if (strncmp("NUMA node(s)", buf, strlen("NUMA node(s)")) == 0) {
// 当遇到包含 "NUMA node(s)" 的行时,解析并获取总 NUMA 节点数量
char* loc = strchr(buf, ':');
*totalNumaNum = pg_strtoint32(loc + 1);
}
}
pclose(fp); // 关闭文件流
}
}
bool ThreadPoolControler::IsActiveCpu(int cpuid, int numaid)
{
switch (m_cpuInfo.bindType) {
case NO_CPU_BIND:
case ALL_CPU_BIND:
// 如果未进行 CPU 绑定,或者进行了全局 CPU 绑定,检查 CPU 是否激活
return (m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset));
case NODE_BIND:
// 如果进行了 NUMA 节点绑定,检查 CPU 和 NUMA 节点是否激活
return (m_cpuInfo.isBindNumaArr[numaid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset));
case CPU_BIND:
// 如果进行了 CPU 绑定,检查 CPU 是否激活
return (m_cpuInfo.isBindCpuArr[cpuid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset));
case NUMA_BIND:
// 如果进行了 NUMA 节点和 CPU 绑定,检查 CPU 和 NUMA 节点是否激活
return (m_cpuInfo.isBindCpuNumaArr[cpuid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset));
}
return false; // 默认情况下,返回 false
}
bool ThreadPoolControler::CheckCpuBind() const
{
if (m_cpuInfo.bindType == NO_CPU_BIND)
return false; // 如果没有进行 CPU 绑定,则返回 false
if (m_groupNum != m_cpuInfo.activeNumaNum) {
ereport(WARNING,
(errmsg("无法将工作线程绑定到 CPU因为线程组数必须等于激活的 NUMA 节点数。")));
return false; // 如果线程组数不等于激活的 NUMA 节点数,返回 false
}
if (m_cpuInfo.activeCpuNum == 0 || m_cpuInfo.cpuArr == NULL) {
ereport(WARNING, (errmsg("无法将工作线程绑定到 CPU因为没有有效的 CPU。")));
return false; // 如果没有有效的 CPU返回 false
}
return true; // 其他情况下,返回 true
}
bool ThreadPoolControler::CheckCpuNumaBind() const
{
return m_cpuInfo.bindType == NUMA_BIND; // 如果进行了 NUMA 绑定,返回 true否则返回 false
}
bool ThreadPoolControler::CheckNumaDistribute(int numaNodeNum) const
{
if (m_cpuInfo.bindType == NO_CPU_BIND) {
ereport(WARNING,
(errmsg("在激活 NUMA 时,应使用 allbind 来替代 nobind 在 thread_pool_attr 中。")));
return false; // 如果未进行 CPU 绑定,给出警告并返回 false
}
if (!CheckCpuBind()) {
return false; // 如果 CPU 绑定检查失败,返回 false
}
if (m_cpuInfo.totalNumaNum != numaNodeNum || !m_cpuInfo.cpuArrSize) {
ereport(WARNING,
(errmsg("无法激活 NUMA 分布,因为没有多个 NUMA 节点或可用的 CPU。")));
return false; // 如果 NUMA 节点数不等于给定的 numaNodeNum或者 cpuArrSize 为空,返回 false
}
for (int i = 0; i < m_cpuInfo.totalNumaNum; ++i) {
if (m_cpuInfo.cpuArrSize[i] <= 0) {
ereport(WARNING,
(errmsg("无法激活 NUMA 分布,因为节点 %d 中没有可用的 CPU。", i)));
return false; // 如果某个 NUMA 节点中没有可用的 CPU返回 false
}
}
return true; // 其他情况下,返回 true
}
CPUBindType ThreadPoolControler::GetCpuBindType() const
{
return m_cpuInfo.bindType;
}
void ThreadPoolControler::SetStreamInfo()
{
m_streamProcRatio = m_stream_attr.procRatio;
m_maxStreamPoolSize = Min(m_stream_attr.threadNum, m_threadNum);
}
void ThreadPoolControler::SetGroupAndThreadNum()
{
if (m_attr.groupNum == 0) {
if (m_cpuInfo.totalNumaNum > 0)
m_groupNum = m_cpuInfo.activeNumaNum; // 如果未指定线程组数且存在 NUMA 节点,则使用激活的 NUMA 节点数
else
m_groupNum = DEFAULT_THREAD_POOL_GROUPS; // 否则使用默认的线程组数
} else {
m_groupNum = m_attr.groupNum; // 如果指定了线程组数,则使用指定的线程组数
}
if (m_attr.threadNum == 0) {
if (m_cpuInfo.activeCpuNum > 0)
m_threadNum = m_cpuInfo.activeCpuNum * THREAD_CORE_RATIO; // 如果未指定线程数且存在激活的 CPU则计算线程数
else
m_threadNum = DEFAULT_THREAD_POOL_SIZE; // 否则使用默认的线程数
} else {
m_threadNum = m_attr.threadNum; // 如果指定了线程数,则使用指定的线程数
}
ConstrainThreadNum(); // 调用 ConstrainThreadNum 方法进行线程数约束
}
void ThreadPoolControler::ConstrainThreadNum()
{
/* 线程池大小不应超过 max_connections。 */
if (MAX_THREAD_POOL_SIZE > g_instance.attr.attr_network.MaxConnections) {
ereport(LOG, (errcode(ERRCODE_OPERATE_INVALID_PARAM),
errmsg("最大线程池大小 %d 不应超过 max_connections %d因此将最大线程池大小减小到 max_connections",
MAX_THREAD_POOL_SIZE, g_instance.attr.attr_network.MaxConnections)));
}
m_maxPoolSize = Min(MAX_THREAD_POOL_SIZE, g_instance.attr.attr_network.MaxConnections); // 最大线程池大小受限于 max_connections
m_threadNum = Min(m_threadNum, m_maxPoolSize); // 线程数不应超过最大线程池大小
}
int ThreadPoolControler::GetThreadNum()
{
return m_maxPoolSize; // 返回最大线程池大小
}
ThreadPoolStat* ThreadPoolControler::GetThreadPoolStat(uint32* num)
{
ThreadPoolStat* result = (ThreadPoolStat*)palloc(m_groupNum * sizeof(ThreadPoolStat)); // 分配存储线程池统计信息的内存
int i;
for (i = 0; i < m_groupNum; i++) {
m_groups[i]->GetThreadPoolGroupStat(&result[i]); // 获取每个线程组的统计信息
}
*num = m_groupNum; // 返回线程组数量
return result; // 返回线程池统计信息数组
}
void ThreadPoolControler::CloseAllSessions()
{
ereport(LOG, (errmodule(MOD_THREAD_POOL),
errmsg("pmState:%d, start to close all sessions in threadpool.", pmState)));
m_sessCtrl->MarkAllSessionClose(); // 标记所有会话为关闭状态
(void)SignalCancelAllBackEnd(); // 发送取消信号以取消所有后端任务
for (int i = 0; i < m_groupNum; i++) {
m_groups[i]->GetListener()->SendShutDown(); // 发送关闭信号给所有监听器
}
/* 检查直到所有组都关闭了它们的会话。 */
bool allclose = false;
while (!allclose) {
if (m_sessCtrl->IsActiveListEmpty()) {
break;
}
allclose = true;
for (int i = 0; i < m_groupNum; i++) {
allclose = (m_groups[i]->AllSessionClosed() && allclose); // 检查每个组的会话是否都已关闭
}
pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用
}
ereport(LOG, (errmodule(MOD_THREAD_POOL),
errmsg("pmState:%d, all threadpool sessions already closed.", pmState)));
}
void ThreadPoolControler::ShutDownThreads(bool forceWait)
{
for (int i = 0; i < m_groupNum; i++) {
m_groups[i]->ShutDownThreads(); // 关闭所有线程组中的线程
}
ereport(LOG, (errmodule(MOD_THREAD_POOL),
errmsg("pmState:%d, shut down all threadpool threads.", pmState)));
if (forceWait) {
/* 检查直到所有组都关闭了它们的工作线程。 */
bool allshut = false;
while (!allshut) {
allshut = true;
for (int i = 0; i < m_groupNum; i++) {
allshut = (m_groups[i]->AllThreadShutDown() && allshut); // 检查每个组的线程是否都已关闭
}
pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用
}
ereport(LOG, (errmodule(MOD_THREAD_POOL),
errmsg("pmState:%d, all threadpool threads already shut down.", pmState)));
}
}
void ThreadPoolControler::ShutDownListeners(bool forceWait)
{
for (int i = 0; i < m_groupNum; i++) {
m_groups[i]->GetListener()->ShutDown(); // 关闭所有线程组的监听器
}
if (forceWait) {
bool allshut = false;
while (!allshut) {
allshut = true;
for (int i = 0; i < m_groupNum; i++) {
allshut = (m_groups[i]->GetListener()->GetThreadId() == 0) && allshut; // 检查监听器线程是否都已关闭
}
pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用
}
}
}
void ThreadPoolControler::ShutDownScheduler(bool forceWait, bool noAdjust)
{
if (noAdjust) {
pg_memory_barrier();
m_scheduler->m_canAdjustPool = false;
}
m_scheduler->ShutDown();
if (forceWait) {
bool allshut = false;
while (!allshut) {
allshut = m_scheduler->HasShutDown();
pg_usleep(one_hundred_micro_sec);
}
}
}
void ThreadPoolControler::EnableAdjustPool()
{
pg_memory_barrier();
m_scheduler->m_canAdjustPool = true;
}
void ThreadPoolControler::AddWorkerIfNecessary()
{
for (int i = 0; i < m_groupNum; i++)
m_groups[i]->AddWorkerIfNecessary();
if (m_scheduler->HasShutDown()) {
m_scheduler->StartUp();
m_scheduler->SetShutDown(false);
}
EnableAdjustPool();
}
ThreadPoolGroup* ThreadPoolControler::FindThreadGroupWithLeastSession()
{
int idx = 0; // 用于记录具有最少会话的线程组的索引
float4 least_session = 0.0; // 用于记录最少会话数
float4 session_per_thread = 0.0; // 用于记录每个线程的平均会话数
least_session = m_groups[0]->GetSessionPerThread(); // 获取第一个线程组的平均会话数作为初始值
for (int i = 1; i < m_groupNum; i++) {
session_per_thread = m_groups[i]->GetSessionPerThread(); // 获取当前线程组的平均会话数
if (session_per_thread < least_session) { // 如果当前线程组的平均会话数更小
least_session = session_per_thread; // 更新最少会话数
idx = i; // 更新具有最少会话的线程组的索引
}
}
return m_groups[idx]; // 返回具有最少会话的线程组的指针
}
bool ThreadPoolControler::StayInAttachMode()
{
return m_sessCtrl->GetActiveSessionCount() < m_threadNum;
}
int ThreadPoolControler::DispatchSession(Port* port)
{
ThreadPoolGroup* grp = NULL;
knl_session_context* sc = NULL;
/*
* 在comm_proxy模式下每个接受的文件描述符fd都与一个
* 固定的通信线程合并在一个NUMA组中我们不再使用旧的方法“
* 与最新会话的组合”进行分配因此只需返回通信线程所在的NUMA组即可。
*
* 注意:我们假设每个连接的用户会话可以等可能地分布给通信器,
* 幸运的是,欧拉操作系统似乎可以保证这一点(已经证明),否则我们还需要重新考虑这个问题。
*
* 性能优化:在使用`comm_proxy`时,当`thread_pool`的`m_groupNum`与`comm_proxy`的NUMA组数相同时可以实现性能优化。
*/
if (AmIProxyModeSockfd(port->sock) && m_groupNum == g_comm_proxy_config.s_numa_num) {
CommSockDesc* comm_sock = g_comm_controller->FdGetCommSockDesc(port->sock);
grp = m_groups[comm_sock->m_group_id];
} else {
grp = FindThreadGroupWithLeastSession();
}
if (grp == NULL) {
Assert(false);
return STATUS_ERROR;
}
/* 如果这个组挂起了,我们不会接受新的会话。 */
if (grp->IsGroupHanged()) {
ereport(WARNING,
(errmodule(MOD_THREAD_POOL),
errmsg("Group[%d] is too busy to add new session for now.", grp->GetGroupId())));
return STATUS_ERROR;
}
sc = m_sessCtrl->CreateSession(port);
if (sc == NULL)
return STATUS_ERROR;
grp->GetListener()->AddNewSession(sc);
return STATUS_OK;
}
/*
* 将指定的线程绑定到所有可用的CPU。
* 此操作由辅助线程例如WAL发送者调用。
*/
void ThreadPoolControler::BindThreadToAllAvailCpu(ThreadId thread) const
{
// 如果不需要绑定CPU直接返回
if (!CheckCpuBind()) {
return;
}
// 如果绑定方式是ALL_CPU_BIND也直接返回
if (m_cpuInfo.bindType == ALL_CPU_BIND) {
return;
}
// 创建一个CPU集合初始化为空
cpu_set_t availCpuSet;
CPU_ZERO(&availCpuSet);
// 遍历每个NUMA节点
for (int numaNo = 0; numaNo < m_cpuInfo.totalNumaNum; ++numaNo) {
int cpuNumber = m_cpuInfo.cpuArrSize[numaNo];
// 将每个NUMA节点上的CPU添加到CPU集合中
for (int i = 0; i < cpuNumber; ++i) {
CPU_SET(m_cpuInfo.cpuArr[numaNo][i], &availCpuSet);
}
}
// 使用pthread_setaffinity_np函数将线程绑定到CPU集合中
int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &availCpuSet);
// 如果绑定失败,输出警告信息
if (ret != 0)
ereport(WARNING, (errmsg("BindThreadToAllAvailCpu fail to bind thread %lu, errno: %d", thread, ret)));
}