【手写数据库内核组件】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://xiahunao.cn/news/3245625.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

[C++] 由浅入深理解面向对象思想的组成模块

文章目录 (一) 类的默认成员函数(二) 构造函数构造函数的特征构造函数示例无参构造带参构造 冲突:全缺省参数的构造函数与无参构造函数 &#xff08;三&#xff09;析构函数特性析构函数的析构过程解析 &#xff08;四&#xff09;拷贝构造函数什么是拷贝构造&#xff1f;特性为…

数据结构——单链表详解(超详细)(2)

前言&#xff1a; 上一篇文章小编简单的介绍了单链表的概念和一些函数的实现&#xff0c;不过为了保证文章的简洁&#xff0c;小编把它分成了两篇来写&#xff0c;这一篇小编紧接上一篇文章继续写单链表函数功能的实现&#xff1a; 目录&#xff1a; 1.单链表剩余函数的编写 1.…

MBR40150FCT-ASEMI无人机专用MBR40150FCT

编辑&#xff1a;ll MBR40150FCT-ASEMI无人机专用MBR40150FCT 型号&#xff1a;MBR40150FCT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 批号&#xff1a;最新 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;40A 最大循环峰值反向电压&#xff08;VRRM&a…

小程序-视图与逻辑

一、页面导航 声明式导航 编程式导航 导航传参 1.声明式导航传参 2.编程式导航传参 3.在onload中接收导航参数 二、页面事件 下拉刷新 上拉触底 三、生命周期 分类 生命周期函数分类 1.应用的生命周期函数 2.页面的生命周期函数 四、WXS脚本 基础语法 wxs的特点 五、案…

智能硬件——0-1开发流程

文章目录 流程图1. 市场分析具体分析 2. 团队组建2. 团队组建早期团队配置建议配置一&#xff1a;基础型团队 (4人)配置二&#xff1a;扩展型团队 (6人)配置三&#xff1a;全面型团队 (7人) 3. 产品需求分析4. ID设计&#xff08;Industrial Design, 工业设计&#xff09;5. 结…

MathType加载项被word禁用怎么办 MathType加载到Word不能用

Word中的MathType加载项是指将MathType软件与Word文档进行关联的一项功能。它允许用户在Word中直接使用MathType的功能&#xff0c;方便地输入和编辑数学公式等内容。通过加载项&#xff0c;MathType的强大数学公式编辑能力可以与Word的文档处理功能相结合&#xff0c;提高工作…

谷歌账号异常的常见8种状态,前三种有望立刻恢复,越往后越难(1)

生搬硬套列夫•托尔斯泰的一句话名言“幸福的家庭都是相似的,不幸的家庭各有各的不幸。” 工作、学习、娱乐中需要使用谷歌账号的朋友可能会发现&#xff0c;幸福的人就是谷歌账号一直都好好的&#xff0c;每次登陆都如所愿&#xff0c;丝滑无比。但是“不幸”的人就会发现&am…

【微信小程序知识点】自定义构建npm

在实际开发中&#xff0c;随着项目的功能越来越多&#xff0c;项目越来越复杂&#xff0c;文件目录也变得很繁琐&#xff0c;为了方便进行项目的开发&#xff0c;开发人员通常会对目录结构进行优化调整&#xff0c;例如&#xff1a;将小程序源码放到miniprogram目录下。 &…

电脑型号数据源的性能提升:新一代技术的突破

随着科技的不断发展&#xff0c;电脑型号的数据源性能也得到了显著的提升。新一代技术的突破使得电脑型号的数据源更加准确、全面且易于使用。本文将从代码的角度解释这一突破&#xff0c;并参考挖数据平台的内容&#xff0c;向大家介绍电脑型号数据源的性能提升。 首先&#…

【scrapy】——个人笔记

scrapy简介 Scrapy&#xff0c;Python开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛&#xff0c;可以用于数据挖掘、监测和自动化测试。 Scrapy吸引人的地方在于它是一个框架&#xff0c;任何人都可…

HEVC编码中的MPM(最可能模式,Most Probable Mode)

简介 最近看到有文章用视频编码时的MPM参数来映射特征并用于数字取证&#xff0c;故做该文章记录。 HEVC&#xff08;高效视频编码&#xff09;中的MPM&#xff08;最可能模式&#xff0c;Most Probable Mode&#xff09;用于预测帧内块的模式&#xff0c;以提高编码效率并减…

【python】PyQt5的窗口界面的各种交互逻辑实现,轻松掌控图形化界面程序

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【ABB】示教器可编程按钮的配置

【ABB】示教器可编程按钮的配置 操作流程演示 操作流程 首先我要配置的是如图所示控制器上的四个按钮&#xff0c;这四个按钮是可以自定义功能的。 点击【菜单】点击【Control Panel】点击【ProgKeys】即可配置对应按键功能 演示 点击【菜单】 点击【配置可编程按键】 这里…

12306高铁票如何打印电子发票?

高铁票报销凭证电子版如何打印&#xff1f; 高铁票报销目前没有电子发票形式&#xff0c;只有纸质版报销凭证&#xff0c;不过虽然没有电子版报销凭证&#xff0c;但是可以通过多种方式获取纸质版报销凭证。 报销凭证只能打印一次&#xff0c;丢失不能补打&#xff0c;请妥善…

cpp 强制转换

一、static_cast static_cast 是 C 中的一个类型转换操作符&#xff0c;用于在类的层次结构中进行安全的向上转换&#xff08;从派生类到基类&#xff09;或进行不需要运行时类型检查的转换。它主要用于基本数据类型之间的转换、对象指针或引用的向上转换&#xff08;即从派生…

Stable Diffusion教程|视频转绘有手就会Ebsynth Utility应用详解

过去这几个月&#xff0c;相信很多同行都已经见识到了AIGC强大的风格化绘图能力&#xff0c;不仅能够做出高品质的美术资产&#xff0c;还可以将制作效率大幅提高&#xff0c;这也是AI概念如今在资本市场受热捧的重要原因。 不过&#xff0c;目前我们所见到的大部分图形&#…

把当前img作为到爷爷的背景图

&#xff08;忽略图大小不一致&#xff0c;一般UI给的图会刚好适合页面大小&#xff0c;我这网上找的图&#xff0c;难调大小&#xff0c;我行内的就自己随便写的宽高&#xff09;&#xff0c;另外悄悄告诉你最后有简单方法&#xff5e;&#xff5e; 先来看看初始DOM结构代码 …

支付宝低代码搭建电商小程序,无需编程,可视化操作

大家好&#xff0c;我是小悟 在数字化浪潮的推动下&#xff0c;为了更快速、高效地搭建电商小程序&#xff0c;支付宝低代码平台凭借其独特优势&#xff0c;为商家提供了便捷的解决方案。 支付宝低代码平台犹如一座精心打造的智慧工坊&#xff0c;让电商小程序的搭建变得轻而易…

硕博电子智能控制器、触摸显示屏在集装箱跨运车上的应用

港口跨运车&#xff0c;又称跨运车或轮胎式龙门吊(RTG)&#xff0c;专门用于集装箱码头的装卸和搬运作业&#xff0c;能够迅速完成集装箱在码头前沿、堆场区域以及仓库之间的运输和堆垛&#xff0c;大幅度缩短了装卸周期&#xff0c;提高了港口物流周转效率。 现代跨运车往往配…

Linux - 冯-诺依曼体系结构、初始操作系统

目录 冯•诺依曼体系 结构推导 内存提高效率的方法 数据的流动过程 体系结构相关知识 初始操作系统 定位 设计目的 操作系统之上之下分别有什么 管理精髓&#xff1a;先描述&#xff0c;再组织 冯•诺依曼体系 结构推导 计算机基本工作流程图大致如下&#xff1a; 输入设备&a…