ClickHouse源码初探(线程池)
这篇笔记围绕 ClickHouse 服务启动链路中的线程模型展开,重点解释了 POCO ThreadPool 与常见线程池设计的差异。POCO 线程池模型认知文章先对比“全局任务队列型”和“每线程事件循环型”两类常见线程池。POCO 更像线程资源池:提供线程获取与执行能力,而非完整任务调度系统。这一点直接影响其在高并发…
POCO 的 thread pool实现 与我目前见过的 thread pool 实现都有所不同,我之前见过的 thread pool 就分为下面两种:多个 thread 跑起来,然后同步争抢一个全局的 task queue 去执行,当需要在线程池里面跑的时候就往 queue 里面塞任务,稍微复杂一点会把各个 thread worker 的状态也完全涵盖。这个 task queue 是 mpmc 的场景,且任务想要执行异步操作必须确保线程安全且线程切换开销很大(类似于协程 await 后后面的任务被调度到其他线程,如果是 CPU bound 型的任务影响会很大)。基于多个 thread 的 EventLoopThreadPool,每个 thread 中都包含自己的 task queue,基于 epoll 或者其他 IO 多路复用来进行 task 的轮询,每个 task 的执行过程是从 thread pool 通过一些负载均衡策略取出一个 thread 后将 task 插入他本地的 task queue 去执行,且对 task 提供的异步能力是继续插入到该 thread 执行,所以无需考虑线程安全。具体实现的时候每个 task queue 需要是一个 mpsc, thread per core 非常适合 CPU bound 的任务或者已经做好负载均衡的任务的执行,我之前写过的 netpoll-cpp 就是这样实现的一个架构。 而 POCO 的 thread pool 实现就很不一样(对我来说),但其实更像线程池,整体上来说是这样的架构: 线程池只提供获取线程让你去执行的能力,不提供任务调度和分配的能力,把线程池当做线程来用即可,只不过从里面取一个线程然后让这个任务在这里面执行。 而我之前所见到的都提供了任务分配和调度的能力,而不是想象中的拿到线程立即执行任务,而是把任务抛进池子里,然后让任务去被调度。 POCO 的实现有下面这些细节:没有用 std 的 thread,而是选择基于 pthread API 重新实现了一个 thread,提供了改变 thread 中任务执行的优先级和 thread 中的栈空间以及 thread local 的自定义存储能力:code,执行任务的代码 code,所有需要在thread 中执行的任务都需要实现 Runnable 这个接口。线程池不直接使用实现好的 thread,而是在其上在封装一层变成 PooledThread ,它提供了更上层的状态,并且 start 后会执行他本身实现的 run 方法,其中会通过 Event 来实现信号量的等待和通知,如果 task 设置好了就通过 targetReady 这个 Event 来通知其执行。而 ThreadPool 正是由这样一个个 PooledThread 组成,而关键的操作是在其上面多了个动态调整大小的能力,以及选择为你选择一个正在空闲的 thread,具体关键代码 codeClickHouse 各协议服务的启动过程 大家可能很好奇,我一个 clickhouse 的源码解析为什么会提到 POCO 的线程池。 在我看的这部分源码中,POCO 的存在感都太高了,所有的服务就是依靠 POCO 的网络库启动起来的,而其中则会用到 ThreadPool,而我最困惑的点在于似乎没有用到 epoll 等多路复用去做 EventLoop,所以记下了这个 ThreadPool 的实现(以前没见过)。 clickhouse server 的启动逻辑是在 programs/server/Server.cpp 这个文件下。 主要经过下面这些步骤的演进来创建对外暴露的服务:首先从 mainEntryClickHouseServer 开始,一路往下转来转去,肯定会把转迷糊,由于DB::Server 这个类型是实现了 Application 这个类的 main 函数,所以最终调用的是他的 main ,这个 main 非常的长,要做的事情也非常的多,我们直接定位到 code 该逻辑用来创建各个协议的 servers。那么其实与创建 servers 服务相关的代码就是这个:code,里面包含了 http、tcp native、pgsql、mysql 等等 协议的服务。我们从 tcp native 协议的代码入口中进入来剥开 clickhouse 的神秘面纱 code。通过 ProtocolServerAdapter 中的 Impl interface 完成对不同协议具体 server 的启动的实现,比如 TCP Native 协议是通过 TCPServer 来提供服务 ,而这个 TCPServer 其实主要就是提供了 TCPServerConnectionFactory 给 POCO…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行