openGauss源码解析(29)

openGauss源码解析:第3章公共组件源码解析(5)

3.4.2 线程池实现

线程池功能由GUC参数enable_thread_pool控制,该变量设置为true时才能使用线程池功能。代码主要在“openGauss-server/src/gausskernel/process/threadpool”目录中,下面介绍主要代码实现流程。

Postmaster线程在ServerLoop中判断如果启用了线程池功能,则会调用“ThreadPoolControler::Init”函数进行线程池的初始化。在线程池初始化时,会判断NUMA节点的个数进行NUMA结构处理。相关代码如下:

if (threadPoolActivated) {

bool enableNumaDistribute = (g_instance.shmem_cxt.numaNodeNum > 1);

g_threadPoolControler->Init(enableNumaDistribute);

}

“ThreadPoolControler::Init”函数的主要作用是创建m_sessCtrl成员和m_groups成员对象,根据绑核策略分配线程个数,调用“ThreadPoolGroup::init”函数进行线程组的初始化,调用“ThreadPoolGroup::WaitReady”函数等待各个线程组初始化结束。创建m_scheduler成员对象,并且调用“ThreadPoolScheduler::StartUp”函数启动线程池调度线程。在“ThreadPoolGroup::init”函数中,创建m_listener对象,启动listener线程。为ThreadWorkerSentry函数分配内存,初始化每个worker的互斥量和条件变量。调用“ThreadPoolGroup::AddWorker”函数创建worker对象,启动worker线程。

Postmaster线程在ServerLoop中如果监听到有客户端链接请求,判断启用了线程池功能,则会调用“ThreadPoolControler::DispatchSession”函数进行会话分发。相关代码如下:

if (threadPoolActivated &&!(i < MAXLISTEN && t_thrd.postmaster_cxt.listen_sock_type[i] == HA_LISTEN_SOCKET))

result = g_threadPoolControler->DispatchSession(port);

/* ThreadPoolControler::DispatchSession的代码实现如下,找到一个会话数最少的线程组,创建会话,把会话添加到线程组的监听线程中 */

int ThreadPoolControler::DispatchSession(Port* port)

{

ThreadPoolGroup* grp = NULL;

knl_session_context* sc = NULL;

grp = FindThreadGroupWithLeastSession();

if (grp == NULL) {

Assert(false);

return STATUS_ERROR;

}

sc = m_sessCtrl->CreateSession(port);

if (sc == NULL)

return STATUS_ERROR;

grp->GetListener()->AddNewSession(sc);

return STATUS_OK;

}

listener线程的主函数为“TpoolListenerMain(ThreadPoolListener* listener)”。在该函数中设置线程的名字和信号处理函数,创建epoll等待事件,通知Postmaster线程已经准备好,调用t_pool_listener_loop函数(其实是调用“ThreadPoolListener::WaitTask”函数进入等待事件状态)。如果有事件到来,调用“ThreadPoolListener::HandleConnEvent”函数找到事件对应的会话。调用“ThreadPoolListener::DispatchSession”函数,如果有空闲的worker线程,通知worker线程进行处理;如果没有空闲的worker线程,则把会话挂到等待队列中。

worker线程的主函数就是正常的SQL处理函数PostgresMain,与非线程模式相比,主要多了3处处理:

(1) worker线程准备就绪的通知。
(2) 等待会话通知。
(3) 连接退出处理。

worker线程的相关代码如下:

if (IS_THREAD_POOL_WORKER) {

u_sess->proc_cxt.MyProcPort->sock = PGINVALID_SOCKET;

t_thrd.threadpool_cxt.worker->NotifyReady();

}

if (IS_THREAD_POOL_WORKER) {

t_thrd.threadpool_cxt.worker->WaitMission();

Assert(u_sess->status != KNL_SESS_FAKE);

}

case 'X':

case EOF:

RemoveTempNamespace();

InitThreadLocalWhenSessionExit();

if (IS_THREAD_POOL_WORKER) {

t_thrd.threadpool_cxt.worker->CleanUpSession(false);

break;

}

“ThreadPoolWorker::WaitMission”函数的主要作用是阻塞所有系统信号,避免系统信号比如SIGHUP等中断当前的处理。清除线程上的会话信息,保证没有上一个会话的内容,等待会话上新的请求,把会话给线程进行处理,允许系统信号中断。

“ThreadPoolWorker::CleanUpSession”函数的主要作用是清除会话,从Listener中去除会话,释放会话资源。

上面介绍了线程池的主要机制,综上所述,线程池主要是解决大并发的用户连接,在一定程度上可以起到流量控制的作用,即使用户的连接数很多,后端也不需要分配太多的线程。线程是OS的一种资源,如果线程太多,OS资源占用很多,并且大量线程的调度和切换会带来昂贵的开销。如果没有线程池,随着连接数的增多,系统的吞吐量会逐渐降低。另外一方面,把线程池划分为线程组,可以很好地匹配NUMA CPU架构的节点,提升多核情况下的访问性能。每个线程组一个监听者,避免了线程池的“惊群效应”。

posted @ 2024-04-29 15:43  openGauss-bot  阅读(28)  评论(0编辑  收藏  举报