【手写数据库内核组件】0501多线程并发模型,任务分发多工作者执行架构实现,多线程读写状态时volatile存储类型使用技巧

0501 多线程管理

专栏内容

  • postgresql使用入门基础
  • 手写数据库toadb
  • 并发编程

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

文章目录

  • 0501 多线程管理
  • 一、概述
  • 二、 原理与机制
  • 三、多条流水线的工厂
    • 3.1 Worker信息结构定义
    • 3.2 工厂的结构定义
    • 3.3 工厂的建立
  • 四、分发任务
  • 五、执行任务
  • 六、总结
  • 结尾

一、概述


现代的CPU都会采用多个core的形式具有并行执行的能力,同一时间可以打开多个应用程序,即使是我们的手机,它的CPU也是非常强大的多核处理器。

如何让我们开发的应用程序充分利用多核CPU呢,这就不得不说多线程模型。

本文就来分享一下多线程模型的搭建与使用。

二、 原理与机制


在应用程序的架构中,一般采用分层原则,业务以任务的形式发布,而执行者接收任务只负责执行,并记录结果。

基于这样的总体架构设计,对于多线程的使用分为两种方式:

  1. 当任务产生时,再启动线程,任务执行完成后,线程也随之结束;
  2. 希望在应用程序启动时,有一定数量的执行者线程就开始处理待命状态,这个数量也决定了应用程序的并发,也就是处理任务的吞吐量;

第一种模式,适合一些任务量不大的业务逻辑,没有业务任务时,不需要占用系统资源;

而对于第二种模式,适合大量任务的场景,频繁的启动和销毁线程反而会带来大量的开销,最好是提前准备好线程,每个线程能执行不同的任务,线程是可重入的。

在这里插入图片描述

对于数据库程序而言,部分业务应用会以短连接的形式连接到数据库,可能执行一条或几条SQL就断开了,面对这样大量的短连接时,数据库内核需要保持一定数量的工作线程,来提升处理性能。

如何让多个线程保持等待状态,同时当有任务时还可以唤醒呢?
下面让一步步分解来看。

三、多条流水线的工厂

对于执行任务的线程,我们叫它Worker线程,它们在程序启动时就会创建,然后不停的执行任务,类似于流水线生产一样。

而在一个工厂会有多条这样的流水线,当工厂接到订单时,就会派发给其中一个生产线,并制定生产计划。

下面我们来看Worker线程的定义和工厂的定义,以及它们的初始化。

3.1 Worker信息结构定义

工作者线程需要记录一些信息,如运行状态,线程ID,还有对应的处理接口等,当然每个工作者会有一个唤醒器,也就是信号量。

typedef enum WORKER_STATE
{TW_IDLE,TW_RUNNING,TW_UNKNOWN
}WS_STATE;typedef struct ThreadWorkerInfo 
{unsigned int tw_threadid;volatile WS_STATE tw_state;SemLock taskIdleLock;TaskProcess taskEntry;
}ThreadWorkerInfo;

说明

  • tw_threadid, 是创建工作者线程的ID;
  • tw_state,工作者线程的状态,运行之后是idle状态;当有任务执行时,为running状态;执行结束后,又回到了idle状态;
  • taskIdleLock,当工作者空闲时,会设置有效,有任务时唤醒工作者。这里可以使用信号量,初始化计数器为0;
  • taskEntry,任务处理接口;当有任务时,调用对应的任务处理接口进行处理;

注意,这里的tw_state的存储类型采用 volatile ,后面会看到这个值会被两个线程修改和访问,因为并没有竞争,所以没有进行加锁保护,为了数据的一致性,每次都会从内存进行读取。

3.2 工厂的结构定义

工厂记录了所有工作者的信息,当有任务产生时,来选择空闲的工作者进行派发。

#define WORK_THREAD_NUM 16
typedef struct ThreadFactoryInfo 
{ThreadWorkerInfo workerInfoList[WORK_THREAD_NUM];
}ThreadFactoryInfo;

说明

  • 工作者数量为静态定义,也可以动态数组的形式定义;
  • 当有任务产生时,遍历数组,找到空闲工作者进行派发;

3.3 工厂的建立

在程序启动时,我们将工厂进行建立,此时流水线工作者准备就绪,都处于空闲状态。

工厂遍历数组,初始化每一个工作者。

int CreeateWorkerThread(ThreadWorkerInfo *work)
{int ret = 0;pthread_t threadId;if(NULL == InitializeSem(0, &worker->taskIdleLock)){return -1;}ret = pthread_create(&threadId, NULL, threadEntry, (void *)worker);if (ret != 0) {return -1;}worker->tw_threadid = (unsigned int)threadId;worker->tw_state = TW_IDLE;worker->taskEntry = NULL;return 0;
}

工作者的初始化说明

  • 信号量的初始化,初始计数器为0;
  • 启动线程,这里的线程的执行入口为threadEntry,在下一小节介绍;
  • 线程的入参为worker信息本身;
  • 初始化线程状态为idle, 此时线程的任务处理接口为NULL;

当然,在程序结束时,我们需要对创建的信号量和线程资源进行回收。

int DestoryWorkerThread(ThreadWorkerInfo *worker)
{int* ret = 0;if(NULL == worker)return 0;if(worker->tw_threadid > 0){pthread_join((pthread_t)&worker->tw_threadid, (void **)&ret);}DestorySem(&workerInfo->taskIdleLock);return 0;
}

线程默认情况下需要通过pthread_join进行回收资源,当然也可以设置为分离状态,这里就不再对线程关注。

四、分发任务

任务的产生和分发,可以由主线程进行,当接收到网络消息或键盘指令后,生成任务,然后进行派发。

在这里插入图片描述

派发的流程

  • 准备任务;
  • 查找空闲工作者;
  • 找到后空闲工作者后,将任务派发给工作者;
  • 唤醒工作者;

代码实现

static ThreadFactoryInfo factory;ThreadWorkerInfo * GetIdleWorker()
{int index = WORK_THREAD_NUM - 1;for(; index >= 0; index --){if(factory->workerInfoList[index].tw_state == TW_IDLE)return &factory->workerInfoList[index];}return NULL;
}int PushTask(TaskProcess taskProc)
{ThreadWorkerInfo *idleWorker = NULL;idleWorker = GetIdleWorker();if(NULL == idleWorker){return -1;}idleWorker->taskEntry = taskProc;worker->tw_state = TW_RUNNING;PostSem(&idleWorker->taskIdleLock);return 0;
}

注意

在派发任务时,要注意操作的顺序;

先赋值任务处理接口和运行状态,再进行唤醒;

这样就不会竞争访问taskEntry,同时在信号量的唤醒操作中默认带有内存同步操作。

五、执行任务

工作者线程创建后,调用线程主函数threadEntry,在此处工作者处于就绪状态。

在这里插入图片描述

代码实现如下:

static void* threadEntry(void *arg)
{ThreadWorkerInfo *worker = (ThreadWorkerInfo*)arg;int ret = 0;if(NULL == worker)return NULL;while(worker->tw_threadid > 0){ret = WaitSem(&worker->taskIdleLock);if(ret < 0){break;}if(NULL != worker->taskEntry){worker->taskEntry(&workerInfo->taskContext);worker->tw_state = TW_IDLE;worker->taskEntry = NULL;}        }return NULL;
}

说明

  • 在线程启动后,会等待信号量的通知;
  • 如果信号量被通知,此时检查任务是否被分发;
  • 有任务时,调用任务处理接口,执行任务;
  • 当任务执行完成后,继续等待信号量通知;

六、总结


本文分享了并发编程模型中,分发-并发执行的经典架构;

在这一架构中,工作者线程通过信号量的等待处理就绪状态;

分发者当有任务产生时,先派发任务,再唤醒工作者。

结尾


非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/47134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解PostgreSql域类型(Domain),灵活定义数据约束,让表结构设计更加严谨

在PostgreSQL中&#xff0c;域&#xff08;Domain&#xff09;是一种用户定义的数据类型&#xff0c;它基于系统内已存在的数据类型&#xff0c;并可以附加约束条件。使用域可以增强数据的完整性和一致性&#xff0c;因为它允许开发者对特定列设定更为具体的规则&#xff0c;比…

LeetCode 链表OJ题

1.消失的数字 题目信息及链接&#xff1a;面试题 17.04. 消失的数字 - 力扣&#xff08;LeetCode&#xff09; 分析&#xff1a; 首先我们看到题目给予了我们一个数组&#xff0c;要求我们找到消失的数字&#xff0c;这个消失的数字指的是所给我们的数组中排序后少掉的数字&…

用go实现限流算法

文章目录 固定窗口优缺点&#xff1a;适用场景&#xff1a;总结&#xff1a; 滑动窗口优缺点&#xff1a;适用场景&#xff1a;总结&#xff1a; 漏桶限流器优缺点&#xff1a;适用场景&#xff1a;总结&#xff1a; 令牌桶优缺点&#xff1a;适用场景&#xff1a;总结&#xf…

【Python】Selenium怎么切换浏览器的页面

我们在爬网使用Selenium进行测试的时候&#xff0c;有时候想要点击浏览器里面的网址&#xff0c;跳到另一个页面上&#xff0c;获取第二个页面的内容。 可是有时候从官网进去&#xff0c;点击跳转到下一个页面以后&#xff0c;却没法定位到下一个页面的元素&#xff0c;这时候就…

Pytorch学习笔记day1—— 安装教程

这里写自定义目录标题 Pytorch安装方式 工作需要&#xff0c;最近开始搞一点AI的事情。但是这个国产的AI框架&#xff0c;实话说对初学者不太友好 https://www.mindspore.cn/ 比如说它不支持win下的CUDA&#xff0c;可是我手里只有3070Ti和4060也不太可能自己去买昇腾就有点绷不…

MongoDB教程(八):mongoDB数据备份与恢复

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言MongoDB 备…

01 机器学习概述

目录 1. 基本概念 2. 机器学习三要素 3. 参数估计的四个方法 3.1 经验风险最小化 3.2 结构风险最小化 3.3 最大似然估计 3.4 最大后验估计 4. 偏差-方差分解 5. 机器学习算法的类型 6. 数据的特征表示 7. 评价指标 1. 基本概念 机器学习&#xff08;Machine Le…

程控电阻器

程控电阻器 由于要测试电阻型温度传感器&#xff0c;一个电阻箱又很贵&#xff0c;就想做一款 程控电阻器 来满足。 设计满足300Ω到400kΩ可调电阻。 设计思路 选择数字电位器去控制电阻输出&#xff0c;最好是精度高&#xff0c;范围大的数字电位器。经过寻找后&#xff0c;发…

Beelzebub过程记录及工具集

文章目录 靶场搭建靶场测试过程安装dirsearch扫描目录wpscan扫描破解 靶场搭建 https://download.vulnhub.com/beelzebub/Beelzebub.zip 下载解压镜像&#xff0c;从vmware打开。 一键式开机即可。 打开后配置网络。 确保网络可达。 靶场测试过程 首先使用nmap扫描网段的存…

深入理解Session和Cookie的作用与联系

深入理解Session和Cookie的作用与联系 1、什么是Cookie&#xff1f;1、什么是Session&#xff1f;1、Session和Cookie的联系4、实际应用场景 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Session和Cookie是两个至关重要的概念&#xff0c…

Abaqus基于CT断层扫描的三维重建插件CT2Model 3D

插件介绍 AbyssFish CT2Model 3D V1.0 插件可将采用X射线等方法获取的计算机断层扫描&#xff08;CT&#xff09;图像在Abaqus有限元软件内进行三维重建&#xff0c;进而高效获取可供模拟分析的有限元模型。插件可用于医学影像三维重构、混凝土细观三维重建、岩心数字化等领域…

商品运营分析

本文对某个品类&#xff08;猫砂&#xff09;在1688的情况&#xff0c;进行一定维度的分析&#xff1a; 内容主要是&#xff1a; 1.品类前景 2.阿里巴巴商家平台和淘宝平台销售&#xff0c;销量分析&#xff08;爬虫获取数据&#xff09; 3.对获取的数据&#xff0c;进行分…

解析 Mira :基于 Web3,让先进的 AI 技术易于访问和使用

“Mira 平台正在以 Web3 的方式解决当前 AI 开发面临的复杂性问题&#xff0c;同时保护 AI 贡献者的权益&#xff0c;让他们可以自主拥有并货币化自己的模型、数据和应用&#xff0c;以使先进的 AI 技术更加易于访问和使用。” AI 代表着一种先进的生产力&#xff0c;它通过深…

二叉树问题,两种解决方法(1遍历 2直接定义名字功能递归

1第一种方法就是另写一个traverse方法&#xff0c;2第二种方法就是把函数名当成已经实现的功能&#xff0c;直接写 1、翻转二叉树 class Solution {public TreeNode invertTree(TreeNode root) {if(rootnull) return null;TreeNode leftinvertTree(root.left);TreeNode righti…

博客都在使用的主题切换使用vue2实现思路

效果展示 步骤 1-变量定义css主题色 2-html初始化主题样式 3-vuex存储主题变量&#xff0c;点击触发修改根元素html的样式 4-method触发方法 mutation使用commit action使用dispatch 5-App组件引入该css文件&#xff0c;使用即可 6-将其加入本地存储&#xff0c;刷新后保持主…

烟雾监测与太阳能源:实验装置在其中的作用

太阳光在烟雾中的散射效应研究实验装置是一款模拟阳光透过烟雾环境的设备。此装置能帮助探究阳光在烟雾中的传播特性、散射特性及其对阳光的影响。 该装置主要包括光源单元、烟雾发生装置、光学组件、以及系统。光源单元负责产生类似于太阳光的光线&#xff0c;通常选用高亮度的…

华为OD算法题汇总

60、计算网络信号 题目 网络信号经过传递会逐层衰减&#xff0c;且遇到阻隔物无法直接穿透&#xff0c;在此情况下需要计算某个位置的网络信号值。注意:网络信号可以绕过阻隔物 array[m][n]&#xff0c;二维数组代表网格地图 array[i][j]0&#xff0c;代表i行j列是空旷位置 a…

Mamori.xyz:基于机器学习的区块链价值提取系统

Mamori.xyz 是一个基于机器学习的自动化区块链价值提取系统&#xff0c;其开创一种通用路径查找器&#xff0c;该工具可用于检测和防御潜在的未知安全风险&#xff0c;Mamori.xyz 也将其称为“未知的未知”&#xff0c;即智能合约中的零日漏洞和新出现的与区块链相关的软件问题…

leetcode-383.赎金信

题源 383.赎金信 题目描述 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。示例 1&…

Qt Creator:C++与Python混合编程

目录 1.前言 2.调用Python前的准备 3.在Qt Creator中配置Python库 4.在Qt Creator中添加Python代码 5.在Qt Creator中运行Python代码 6.运行效果 前言 在进行软件开发过程中&#xff0c;我们一般都是在特定的环境下特定的开发语言下进行编程。但是在开发中总有特殊情况&#xf…