揭秘MySQL线程池内幕( 三 )


  • 检查线程组的负载情况进行工作线程的唤醒与创建 。
  • 检查与处理超时的客户端连接 。
这里主要介绍第一部分工作也就是Check Stall机制 。Timer Thread周期性地检查线程组内的线程是否被阻塞(stall) , 所谓阻塞也就是新来了任务但是线程组内没有线程来处理 。Timer Thread通过queue_event_count和IO任务队列是否为空来判断线程组是否为阻塞状态 , 每次工作线程检索任务的时候queue_event_count都会累加 , 累加意味着任务被正常处理 , 工作线程正常工作 , 在每一次check_stall之后queue_event_count会被清零 , 因此如果在一定时间间隔(stall_limit)后的下一次迭代中 , IO任务队列不为空并且queue_event_count为空 , 则说明这段时间间隔内都没有工作线程来处理IO任务了 , 那么Check Stall机制会尝试着唤醒或创建一个工作线程 , 唤醒线程的逻辑很简单 , 如果waiting_threads中有空闲线程则唤醒一个空闲线程 , 否则需要尝试创建一个工作线程 , 创建线程不一定会创建成功 , 我们看看创建线程的条件:
  • 如果没有空闲线程且没有活跃线程则立马创建 , 这个时候可能是因为没有任何工作线程或者工作线程都被阻塞了 , 或者是存在潜在的死锁 。
  • 否则如果距离上次创建的时间大于一定阈值才创建线程 , 这个阈值由线程组内的线程数决定 。
阈值与线程组内线程数的关系如下:
线程数 阈值< 40< 850 * 1000< 16100 * 1000>= 16200 * 1000
阈值机制能够有效的防止线程创建过于频繁 。这里遗留个问题 , 为什么阈值依赖于线程池的线程数?阈值是否能依赖于thread_pool_stall_limit的值?Check Stall机制可以被认为一个专门的线程做专门的事情 , 毕竟线程组内部逻辑也是蛮混乱的 。
任务队列
任务队列也就是listener每次从poolfd轮训出来的就绪任务 , 分为优先任务队列(high_prio_queue)和普通任务队列(queue) , 优先队列中的IO任务会先被处理 , 然后普通队列中的任务才能够被处理 。那么什么样的任务会被认为是优先任务呢?官方列出了两个条件:
  • 连接处于事务中 。
  • 连接关联的priority tickets值大于0 。
【揭秘MySQL线程池内幕】参数priority tickets(thread_pool_high_prio_tickets)的设计是为了防止高优先级的任务总是被处理 , 而一些非高优先级的任务处于较长时间的饥饿状态 , 毕竟工作线程的创建也是有条件的 , 当高优先级的任务每一次被放入高优先级队列之后都会对priority tickets的值进行减一 , 因此达到一定次数priority tickets的值必然会小于等于0 , 因此避免了永久高优先级的问题 。另外队列的使用受参数thread_pool_high_prio_mode影响 , 可参考对参数thread_pool_high_prio_mode介绍的部分 。当就绪IO任务被轮训出来放入队列之后会对io_event_count进行累加 , 当IO任务从队列取出处理的时候会对queue_event_coun进行计数 。
Listener线程
Listener做的事情主要是从poolfd中轮训与其绑定的socket句柄的就绪IO事件 , 事件以任务的形式被放入任务队列并做相应处理 , 如果listener读取了一些IO任务之后 , 该怎么办呢?下面基于两个问题回答:
  • listener应该自己处理这些任务吗?还是将这些任务放入队列让工作线程处理?
  • 如果任务队列不为空 , 我们需要唤醒多少个工作线程?
对于第一个问题 , 通常我们不想经常改变listener的等待和唤醒的状态 , 因为listener刚被唤醒 , 因此我们更倾向于让listener利用它的时间片去做一些工作 。如果listener不自己处理工作 , 这意味着其他线程要被唤醒去做这个工作 , 这显然不是很好 。而让listener去做任务潜在的问题是线程组有可能一段时间网络任务无法及时被处理 , 这不是主要的问题 , 因为stall将被Timer Thread检查 。然而总是依赖Timer Thread也是不好的 , 因为stall_limit有可能被设置比较长的时间 。我们使用下面的策略 , 如果任务队列不空 , 我们任务网络任务此时可能比较多 , 让其他线程来处理任务 , 否则listener自己处理任务 。
对于第二个问题 , 我们通常为每一个线程组保持一个活动线程(活动线程包括正在做任务的线程) , 因此唤醒一个工作线程的条件为当前活跃前程数为0 , 如果没有线程被唤醒 , 在只能依靠Timer Thread来检查stall并进行唤醒了 。


推荐阅读