这篇文章中提到了 tsched 的源码可以一读,所以去阅读了一下,总共220来行。

1. 阅读前工作

通过上文了解到这段程序实现的是一个任务队列,同时带有线程池。这段程序是计算机操作系统里经典的consumer-producer (生产者-消费者)问题的实现。凡是学过操作系统这门课的,都应该知道这个问题,做过习题。在阅读源码之前可以先尝试用伪代码实现上述生产者-消费者问题。

2. 如何阅读?

了解清楚使用场景

这是一个线程池,客户端可以提交任务,线程池按照顺序调度执行任务。通过阅读 tsched.h 头文件,知道主要有三个函数:

  1. 初始化命名的调度器、线程池:taosInitScheduler
  2. 生产者提交某个任务:taosScheduleTask
  3. 程序结束时的清理工作:taosCleanUpScheduler

通过搜索上述三个函数的调用, 知道初始化了两个调度器,有三个地方会提交任务。

两个线程池

  1. 定时器里的 tmr 线程池 : 队列长度一万,只有一个线程服务。此线程会执行到期的 timer回调函数
  2. tsc 线程池:队列长度一万,线程数量为所在机器 CPU 核心数的一半。这些线程负责:异步操作如执行语句固定大小滑动窗口流式数据处理

两个生产者

上面提到了,有三个生产者会提交任务给线程池:

  1. timer
  2. stream

了解了清楚使用方、使用场景后,就容易读懂逻辑了。这里是一个标准的操作系统中生产者消费者的问题,用的也是标准解法:使用一个互斥量,两个信号量。线程池使用 pthread 来创建。

关键的数据结构

SSchedQueue 里面就是上述问题中的核心数据结构,除了放置上述提到的互斥量,信号量,还需要一个队列来存储要具体执行的任务。

SSchedMsg 结构来表示线程池任务,包含要执行的具体函数及所需参数。

源码里注释并不多,只能通过看具体实现来了解上述支持的执行模式。看到支持两种模式:执行fp,或者执行 tfp(ahandle, thandle)。

核心调度逻辑

上面提到了生产者,一直没有提到消费者。接着读 sched.c 里的源码,可以看到消费者就是线程池里每个线程的主框架逻辑: taosProcessSchedQueue。平常这些线程处于阻塞状态,等待任务。一旦生产者提交任务后,就会通知到消费者。消费者拿到提交的任务及参数,去执行。执行完之后继续进入上述阻塞的状态,这样周而复始。

这里有个疑问,消费者和生产者之间是异步的。消费完之后,总得有办法通知消费者,这一步在哪里做呢?读到这里可以花点时间翻翻源码,找找答案。

其实秘密也藏在当时提交任务的数据结构里。TDengine 里有样例代码,翻了翻,找到了这个 async demo。可以看到 taos_query_a 就是一个异步的query函数,里面带了 query语句异步执行完成后的回调函数:taos_insert_call_back)。

3. 一些思考

看的时候内心不断在思考、对比,比如优势、劣势是什么?我会怎么实现

优势

为何使用线程池?

  1. 通过固定线程池大小来固定资源开销,而且是程序初始化时申请资源,这在嵌入式设备里是非常重要的,如果资源不够用,那就快速失败,在程序一开始启动时就报错。
  2. 复用了线程,因为创建、销毁线程都是有开销的。这样在频繁创建、销毁线程情况下,可以节省开销,复用之前的线程。
  3. 任务和线程解耦:需要使用多线程的地方,只管提交任务就好了。线程的初始化、运行、状态切换由线程池来负责。

劣势

  1. 操作异步化,对程序员的心智要求更高。需要使用回调函数,需要存储上下文。但是在上述场景里还好, 都是一些固定的逻辑。
  2. 调试较麻烦,不是直来直去的逻辑。需要通过分析上下文及回调函数里的日志来分析问题。

有没有其他实现方式?

如果用 Go 语言实现,会很简单。使用 channel 来做任务分发,本身就是线程安全的。

使用 C 来写,个人觉得会限制 TDengine 的开源参与方。因为现在市场上会 C 的人比较少,而且主要集中在嵌入式领域。而且 C 的生态一般,语言的轮子比较少,所以很多工作都需要自己做,比如 http server,rpc 等。如果让我来设计实现 TDengine,我可能会优先考虑 Rust,既能精准控制内存,又有比较完善的社区,而且语言处于上升期,容易成为其中的明星项目,会有推广优势,比如能吸引一些本身对数据库不怎么关注,但是对 Rust 感兴趣的程序员。

4. 一个思考题

通过搜索 pthread_create 可以发现系统中还有其他创建线程的地方,并没有用到上述的线程池,比如 dnodeMWrite, TcpPoolcachesync等。这些地方为什么没有使用线程池呢?

源码解读 TDengine 中线程池的实现的更多相关文章

  1. 硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理

    前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章.之前在分析扩展线程池实现可回调的Future时候曾经提到并发 ...

  2. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  3. Java并发包源码学习系列:线程池ScheduledThreadPoolExecutor源码解析

    目录 ScheduledThreadPoolExecutor概述 类图结构 ScheduledExecutorService ScheduledFutureTask FutureTask schedu ...

  4. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  5. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  6. 从源码角度来分析线程池-ThreadPoolExecutor实现原理

    作为一名Java开发工程师,想必性能问题是不可避免的.通常,在遇到性能瓶颈时第一时间肯定会想到利用缓存来解决问题,然而缓存虽好用,但也并非万能,某些场景依然无法覆盖.比如:需要实时.多次调用第三方AP ...

  7. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

  8. 源码角度分析-newFixedThreadPool线程池导致的内存飙升问题

    前言 使用无界队列的线程池会导致内存飙升吗?面试官经常会问这个问题,本文将基于源码,去分析newFixedThreadPool线程池导致的内存飙升问题,希望能加深大家的理解. (想自学习编程的小伙伴请 ...

  9. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

随机推荐

  1. 题解 P1407

    建图方式:旧关系女人连男人,现关系男人连女人(当然,反过来也可以) 原因可以这样考虑: 如果一个男的把女的绿了,那么这个女人就会去找一个她曾经交往过的男人,也就是在这种情况下,某种"影响&q ...

  2. Golang Gtk+3教程:Grid布局

    在上个例子中我们使用了box布局,现在让我们来学习另一种布局--grid.其实这几种布局都大同小异,如果你看懂了上一个例子,想必使用grid也不是难事. 程序运行效果: package main im ...

  3. LOJ10048. 「一本通 2.2 练习 4」Censoring

    作者是个* 题目描述 原题来自:USACO 2015 Feb. Silver 给出两个字符串\(S\)和\(T\),每次从前往后找到\(S\)的一个子串\(T\)并将其删除,空缺位依次向前补齐,重复上 ...

  4. MapReduce之MapJoin案例

    @ 目录 使用场景 优点 具体办法:采用DistributedCache 案例 需求分析 代码实现 使用场景 Map Join 适用于一张表十分小.一张表很大的场景. 优点 思考:在Reduce 端处 ...

  5. Python 常用的操作文件代码

    1:统计list中相同的个数,并按大小排序. original_list = ['a', 'b', 'b', 'a', 'd', 'd', 'b', 'z', 'c', 'b', 'r', 's', ...

  6. Flutter简介

    Flutter简介 Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台.高保真.高性能.开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平 ...

  7. Socket原理及实践(Java/C/C++)

    原理 基本概念 什么是TCP/IP.UDP? 详细的可以看一下这个:学习计算机网络知识只要一篇就够了! TCP/IP(Transmission Control Protocol/Internet Pr ...

  8. JavaScript学习系列博客_31_JavaScript Math 工具类

    Math - Math属于一个工具类,它不需要我们创建对象(例如Date日期对象需要通过构造函数创建对象 var 变量=new Date(),Math不需要),它里边封装了属性运算相关的常量和方法 我 ...

  9. chrome浏览器截图

    1.F12 打开开发者工具台 2.ctrl + shift + p,弹出搜索框之后输入: full 3.选中Mobile-- Capture fullsize screenshot ,成功保存图片.

  10. 算法-排序(1)k路平衡归并与败者树

    const int MaxValue=; //根据实际情况选择最大值 void kwaymerge(Element *r,int k){ int i,q; r=new Element[k]; //在败 ...