mariadb 5.5 threadpool 源码分析

本文集中讨论mariadb 5.5中线程池的基本原理和实现细节

简单对比一下两种线程调度方案

one_thread_per_connection
每个连接在其生命周期内都是由server从thread cache中取出一个(如果有可用的话)或者创建一个线程来为其服务,之后这个线程或者被放到thread cache(和thread cache size有关)或者被释放

threadpool
由一定数目的worker线程处理所有的连接,并发连接数可以大于worker线程数目,连接发送过来的event(处理请求)被放到队列中,worker线程不断从队列中取出event,调用do_command执行查询和返回结果

对于并发量大的业务场景,one_thread_per_connection将会在短时间内创建大量的线程,一方面造成系统资源的大量消耗,另一方面大量并发的线程会加剧系统锁竞争和线程上下文切换代价,从而使得系统性能下降,threadpool调度方案能够利用较少数目的线程处理大量并发的连接,能够极大提高系统系性能,尤其适用于高并发、短事务场景


实现细节:

1、整个连接池内部被分成N个小的group,N默认为cpu的个数,可以通过参数thread_pool_size设置,group之间通过round robin的方式分配连接,group内通过竞争方式处理连接,一个worker线程只属于一个group

2、每个group有一个动态的listener,worker线程在循环取event时,发现队列为空时会充当listener通过epoll的方式监听数据,并将监听到的event放到group中的队列

3、延时创建线程,group中的活动线程数为0或者group被阻塞时,worker线程会被创建,worker线程被创建的时间间隔会随着group内已有的线程数目的增加而变大

4、worker线程, 数目动态变化,这也是相对于5.1版本的一个改进,并发较大时会创建更多的worker线程, 当从队列中取不到event时work线程将休眠,超过thread_pool_idel_timeout后结束生命

5、timer线程,它会每隔一段时间做两件事情:1)检查每个group是否被阻塞,判定条件是:group中的队列中有event,但是自上次timer检查到现在还没有worker线程从中取出event并处理;2)kill超时连接并做一些清理工作

线程池调用接口:

点击(此处)折叠或打开

  1. static scheduler_functions tp_scheduler_functions=
  2. {
  3.   ...
  4.   tp_init,  // init
  5.   NULL,  // init_new_connection_thread
  6.   tp_add_connection,  // add_connection
  7.   tp_wait_begin,  // thd_wait_begin
  8.   tp_wait_end,  // thd_wait_end
  9.   tp_post_kill_notification,  // post_kill_notification
  10.   NULL,  // end_thread
  11.   tp_end  // end
  12. };

以上函数接口在被调用时都会传入一个THD,其中包含了其网络连接的所有信息,如:socket fd

tp_init
线程池初始化工作,对每个group初始化pollfd,启动timer线程

tp_add_connection
根据thd的thread_id采用round robin方式将其分配到一个group中,将thd封装到connection_t的一个结构体中,放到group中的队列中,入队时会判断group中的活动worker线程数(thread_group->active_thread_count)是否大于0,否则将唤醒或者创建一个worker线程(wake_or_create_thread)

worker_main
worker线程函数,逻辑很简单,循环从队列中取get_event,然后调用handle_event

点击(此处)折叠或打开

  1. ...
  2.   for(;;)
  3.   {
  4.     connection = get_event(&this_thread, thread_group, &ts);
  5.     if (!connection)
  6.       break;
  7.     this_thread.event_count++;
  8.     handle_event(connection);
  9.   }
  10. ...
get_event
从队列中取event,取不到则充当listener通过epoll从网络监听事件,如果最终还是没能取到event,休眠一段时间,直到被唤醒或者超时退出

handle_event
做两件事情:1)连接验证;2)处理连接请求直到连接空闲,为空闲连接设置一个超时期限,之后将连接的socket fd绑定到group中的epollfd

timer_thread
做了两件事情:1)检查是否有group被阻塞,阻塞将调用wake_or_create_thread;2)检查是否有连接超时,超时将调用tp_post_kill_notification

点击(此处)折叠或打开

  1. ...
  2.   for (;;)
  3.   {
  4.     /* Check stalls in thread groups */
  5.     for(i=0; i< array_elements(all_groups);i++)
  6.     {
  7.       if(all_groups[i].connection_count)
  8.         check_stall(&all_groups[i]);
  9.     }
  10.      
  11.     /* Check if any client exceeded wait_timeout */
  12.     if (timer->next_timeout_check <= timer->current_microtime)
  13.       timeout_check(timer);
  14.   }
  15. ...
wake_or_create_thread 
唤醒或者创建线程,被三处调用:
1)connection加入到queue中时,group中的队列中没有活动的worker线程:
tp_add_connection
|->queue_put
  |->wake_or_create_thread 

2)timer线程检查是否有group阻塞,满足两个条件中的任一个即被调用:group中没有listener并且队列为空;group被阻塞
timer_thread
|->check_stall
  |->wake_or_create_thread 

3)事务被阻塞(如:锁等待)通过MYSQL_CALLBACK接口调用,等待之前会确定group中有活动的worker线程,否则唤醒或者创建worker线程
tp_wait_begin
|->wait_begin
  |-> |->wake_or_create_thread

更多的细节就不一一在此详述了,深入研究还得:有码有真相

几个和线程池相关的参数:
thread_pool_size:线程池中group的数目
thread_pool_max_thread:最大worker线程数目
thread_pool_idle_timeout:连接超时
thread_pool_stall_limit:timer线程工作的时间间隔
thread_pool_oversubscribe:一个group中的最大同时活动的worker线程数目

参考:
mariadb 5.5.28源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值