一步一步写线程之五线程池的模型之一领导者追随者模型

一、线程池的模型

在学习过相关的多线程知识后,从更抽象的角度来看待多线程解决问题的方向,其实仍然是典型的生产和消费者的模型。无论是数据计算、存储、分发和任务处理等都是通过多线程这种手段来解决生产者和消费者不匹配的情况。所以,得到的结论就清晰了,生产者和消费者根本不匹配的情况下,包括多线程编程在内的各种手段都是无法解决实际问题的。这一点很简单,可是在实际编程过程中,仍然有很多人专注于细节和业务,忽视了这个问题。
模型的意义在于,在看似不匹配的的场景下,通过模型优化实现匹配。这句话有点绕,举个例子就明白了。比如组装100台机器,一个人一天可以组装一台。那么,小学生都可以知道,要想组装完成这些机器,要么100人同时工作,要么1人干一百天。但是现在人都明白了,使用流水线作业可以极大的提高生产效率,即组装这一台机器有30个环节,那么可以找30个人,每人处理一个环节,那么可能1天就完成了。假如某个环节耗时长,某个环节耗时短,那么可以通过最优路径和分治法再次优化,将短和长多对一对应起来,则速度会更快。
模型的意义本质和其没有不同。那么回到线程池的模型来,线程的模型的意义,就是让线程池的使用更高效。那么既然提到模型,其实大家可以很容易的想到,模型的数量肯定不会多,至少在抽象层次的模型上,不会很多。
一般来说,线程池的模型有两大类:
1、领导者-追随者模型
即Leader-Follower模型,既线程池的线程有三种身份,领导者(Leader),追随者(Follower)和工作者(Worker),整个线程池中只有一个领导者,负责管理各种动作,当有事件发生时,其通知Follower们选出一个新Leader,然后自己转变成Worker干活,干活完成重新成为Follower;追随者是线程池启动时除Leader之外的所有线程,它们类似于侯选人,随时等待成为领导者。
这种模型适合于高并发,频繁的小任务动作,比如IM中的聊天信息和HTTP中的打开网页等等。如果任务的耗时长,数据量大等情况时就不适合了。它主要是通过优化线程间的数据复制和上下文调度以及可以增加缓存的命中率等来提高处理的效率。
2、半同步半异步模型
即HA/HS(Half-Sync/Half-Async),即使用线程池处理并发,一部分使用异步,一部分使用同步。比如在IO处理中,IO可以使用异步,但数据任务可以使用队列同步控制。也就是说,整个HA/HS分为三层,异步IO层(与IO异步通信),队列缓冲层(数据任务缓存),同步处理(从队列同步获取数据并处理)。
最终数据会通过异步(回调、消息、事件等)或者同步(管道、返回值等)等待由最上层的客户端拿到数据。
这种模型的变种非常多,因为实际应用的场景非常多。很多开发者为了更好的适应自己的应用场景对其进行各种改变。而且异步IO的机制本身就在不同的平台的实现有着各种情况,比如人们经常提到的前摄器(Proactor)和反应器(Ractor)。
通过描述就明白,在实际的应用场景中,这种方式非常多,换句话说,这种模型基本可以全覆盖,如果实在效率不满足可以再替换成LF模型。

二、LF的分析

本篇重点分析一下Leader-Follower模型。最典型的线程池的应用场景莫过于服务端的高并发编程了,先看一下其转换图:
在这里插入图片描述

在这种模型中,需要注意的是Leader的控制是通过锁来实现的,它有两种机制的方法来实现,一种是直接选举,只有成了领导者进行监听处理,在Ngix中就有类似的实现;另外一种就是使用一个线程竞争得到等待事件的调度,等到后自动升级到Leader。不管如何实现,需要明白是,Leader只有一个,一山只能容一虎嘛。

三、LF应用

明白了LF的基本信息,下面看一个基本的线程池的应用:

// LF-Threadpool.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <thread>
#include <mutex>
#include <memory>
#include <vector>
#include <condition_variable>
#include <atomic>
#include <Windows.h>
#include <mutex>
#include <condition_variable>constexpr int kTHREAD_NUM = 3;
thread_local int LEADER = 0;class ThreadCondition
{
public:ThreadCondition() {}~ThreadCondition() {}
public:inline bool Wait(int  timeOut){signaled_ = false;std::unique_lock<std::mutex> lock(this->lockMutex_);if (this->cvLock_.wait_for(lock, std::chrono::milliseconds(timeOut)) == std::cv_status::timeout){return false;}return true;}inline void Wait(){signaled_ = false;std::unique_lock<std::mutex> lock(this->lockMutex_);while (!signaled_){this->cvLock_.wait(lock);}}inline void Signal(){std::unique_lock<std::mutex> lock(this->lockMutex_);signaled_ = true;//pthread_cond_broadcast(&_cond);this->cvLock_.notify_one();}void SetSignal(bool quit = false)noexcept{//设置退出循环标志if (quit){this->quit_ = true;}//唤醒线程this->Signal();}
private:bool signaled_ = false;std::mutex lockMutex_;std::condition_variable cvLock_;bool quit_ = false;
};class ThreadPool
{
public:ThreadPool() = default;~ThreadPool(){this->tc_.SetSignal(true);for (auto& pthread : this->vecThread_){if (pthread != nullptr && pthread->joinable()){pthread->join();}}}
public:void init() {for (int num = 0; num < kTHREAD_NUM; num++){std::unique_ptr<std::thread> pThread =std::make_unique<std::thread>(&ThreadPool::Work,this,num);this->vecThread_.emplace_back(std::move(pThread));Sleep(600);}}void Run(int flag){std::cerr<<"thread "<<std::this_thread::get_id() << ",start working......" << std::endl;Sleep(600);this->EnterFollower(1);}void SelectLeader(){this->tc_.Signal();}void Work(int flag){LEADER = flag;auto id = std::this_thread::get_id();if ( LEADER == 0) {this->LeaderReady(id);		}else{std::cerr << "cur thread is Follower,thread is :" << id << std::endl;tc_.Wait();this->LeaderReady(id);}}void SetWork(){tcWorking_.Signal();}
private:void EnterFollower(int flag){auto id = std::this_thread::get_id();LEADER = 1;std::cerr << "this thread id:" << id << ",cur thread is follower!start wait....." << std::endl;tc_.Wait();LEADER = 0;std::cerr << "this thread id:" << id << " ,become leader!" << std::endl;}void LeaderReady(std::thread::id id){std::cerr << "cur thread is Leader,id is:" << id << std::endl;std::cerr << "wait for work......." << std::endl;tcWorking_.Wait();std::cerr << " select next follower become worker!" << std::endl;SelectLeader();Run(0);}
private:std::vector<std::unique_ptr<std::thread>> vecThread_;ThreadCondition tc_;ThreadCondition tcWorking_;};
class ThreadManager
{
public:ThreadManager(){this->tPool_ = std::make_unique<ThreadPool>();}~ThreadManager() = default;
public:void Start(){this->tPool_->init();}void SetWorker(){std::cerr << "监听到事件,Leader开始工作......" << std::endl;tPool_->SetWork();}private:std::unique_ptr<ThreadPool> tPool_ = nullptr;std::mutex mutex_;std::atomic<bool> quit_ = true;
};int main()
{ThreadManager tm;tm.Start();Sleep(2000);tm.SetWorker();//触发主线程工作Sleep(2000);tm.SetWorker();Sleep(2000);tm.SetWorker();system("pause");
}

这是一个非常简单的应用,线程启动后有一个线程自动成为Leader,然后其在接收到事件通知后自动通知一个Follower成为Leader,而其自身变成Workder,完成后又转变成Follower,等待机会转变为Leader。如此往复循环,复杂的线程池的应用,基本也是这个框架。
本来是想用上次工程中的代码来实现,但是觉得可能不容易体现出这三个状态的转换,所以就写了一个简单的线程池来体现,线程的等待模拟的是工作时间和触发时间,运行后会发现线程的调度是随机的,但是是在三个线程中轮换的。
也可以将Work函数中的启动就有一个Leader修改为启动均为Follower,然后等待事件通知某一线程升级为Leader。

四、总结

学习理论就是学习别人抽象出来的知识,然后再把学习到的知识理论应用到自己的工作中。如此往复循环,慢慢就会对这些知识有了更深刻的理解,也就可以在此基础上自己抽象自己的理论和知识体系来指导自己的实际工作。
武林中不是有一句话:“练拳不练功,到老一场空;练功不练拳,到老也枉然!”。计算技术亦是如此。

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

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

相关文章

软件测试的工作描述

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

蓝桥杯官网题目:2.包子凑数

链接:题目点这里 首先要知道一个数学定理裴蜀定理&#xff0c;还有完全背包的基本运用&#xff0c;这里只介绍前者 也可以看一下我的个人理解&#xff0c;我是第一次听说这个定理&#xff0c;理解可能有误差。 假设gcd(a,b)d,gcd是最大公约数的意思。即a&#xff0c;b的最大…

fabric.js 组件 图片上传裁剪并进行自定义区域标记

目录 0. 前言 1. 安装fabric与引入 2. fabric组件的使用 3. 属性相关设置 4. 初始化加载 4. 方法 5. 全代码 0. 前言 利用fabric组件&#xff0c;实现图片上传、图片”裁剪“、自定义的区域标记一系列操作 先放一张效果图吧&#x1f447; 1. 安装fabric与引入 npm i …

随身WiFi到底能不能买?一篇文章给你讲清楚!随身WiFi哪个品牌最靠谱 ,随身WiFi推荐第一名

随着移动设备的普及&#xff0c;人们对无线网络的需求越来越高。传统WiFi虽然覆盖面广&#xff0c;但移动性差&#xff0c;不能满足人们在外出、旅行或商务场合的上网需求。此时&#xff0c;随身WiFi的出现填补了这一空白。那么&#xff0c;随身WiFi究竟有何优势和劣势&#xf…

Zabbix 系统监控详解

1 介绍 1.1 摘要 本文深入浅出&#xff0c;切近实际运维应用&#xff0c;由 zabbix 3.4 版本入手&#xff0c;学习 zabbix 监控告警实现方式&#xff0c;由 zabbix 5.0 浅出实现快速部署、快速应用。本人从业多年&#xff0c;关注 zabbix 开源社区&#xff0c;以及 zabbix 官…

【开发必备】泳道图编辑工具及使用

1.什么是泳道图 事情的起因在与博主要和几位小伙伴一起开发一个小程序&#xff0c;那么涉及的人多时就需要用到需求文档这个玩意。然后博主当然要扛起写需求文档这项项目经理 &#xff08;牛马&#xff09;的职责了&#xff01; 然后&#xff0c;博主就发现需求文档中一个看似…

idea上传本地项目到gitlab

1. idea上传本地项目到gitlab 1. 配置idea里本地安装的git位置 即选择 Settings -> Version Control -> Git -> Path to Git executable 2. 在idea创建本地仓库 即选择 VCS -> Create Git Repository 然后选择目录&#xff0c;默认就是选择的当前项目&#xff…

【Linux】03 GCC编译器的使用

一、编译过程 在使用gcc编译程序时&#xff0c;编译过程可以简要划分为4个阶段&#xff1a; 预处理、编译、汇编、链接 1.1 预处理&#xff08;preprocessing&#xff09; 这个阶段主要处理源文件中的#indef、#include和#define预处理命令&#xff1b; 这里主要是把一些include…

Linux指令(四)

1.more指令 我们知道cat指令是用来读取文本文件的&#xff0c;但是如果是大文件&#xff0c;其实是不适合cat读取的&#xff0c;原因是&#xff1a;cat读取会直接到文本的结尾&#xff0c;所以我们引入&#xff1a;more指令 该指令不会将文件直接读到结尾&#xff0c;而是将最…

Python中二维数据(数组、列表)索引和切片的Bug

Python中有关数据结构索引和切片引起的Bug 一维数据索引和切片一维数组一维列表 二维数据的索引和切片二维数组二维(错误)列表 一维数据索引和切片 一维数组 对于一维数据进行索引和切片操作&#xff0c;大家都比较熟悉通过下面代码进行实现 import numpy as np data np.ra…

[陇剑杯 2021]webshell

[陇剑杯 2021]webshell 题目做法及思路解析&#xff08;个人分享&#xff09; 问一&#xff1a;单位网站被黑客挂马&#xff0c;请您从流量中分析出webshell&#xff0c;进行回答&#xff1a; 黑客登录系统使用的密码是_____________。 题目思路&#xff1a; 分析题目&…

logstack 日志技术栈-02-ELK 的缺点?loki 更轻量的解决方案?

ELK/EFK日志系统 如果今天谈论到要部署一套日志系统&#xff0c;相信用户首先会想到的就是经典的ELK架构&#xff0c;或者现在被称为Elastic Stack。 Elastic Stack架构为Elasticsearch Logstash Kibana Beats的组合&#xff0c;其中&#xff0c;Beats负责日志的采集&…

假设检验:以样本服从二项分布举例

目录 假设检验一、假设检验的思想二、假设检验的基本步骤1. 确定要进行检验的假设2. 选择检验统计量3. 确定用于做决策的拒绝域4. 求出检验统计量的值5. 查看样本结果是否位于拒绝域内6. 做出决策 三、举例说明例子1——某公司治疗打鼾例子2——女士品茶的故事 假设检验 一、假…

用Photoshop来制作GIF动画

录了个GIF格式的录屏文件&#xff0c;领导让再剪辑下&#xff0c;于是用Photoshop2023&#xff08;PS版本低至CS6操作方式一样&#xff09;进行剪辑&#xff0c;录屏文件有约1400帧&#xff0c;由于我处理的帧数太多&#xff0c;PS保存为GIF格式时&#xff0c;还是挺耗时的&…

【Docker】网络配置及自定义网络的使用

一、引言 1、什么是网络配置 Docker的网络配置主要是指Docker容器与外部网络之间的连接设置&#xff0c;包括容器内部的IP地址、端口号等。Docker提供了多种网络模式&#xff0c;包括bridge、host、none等&#xff0c;以满足不同的需求。 默认情况下&#xff0c;Docker使用brid…

用Python替代Mapinfo更快查找两张表中距离最近的点

目录 一、引言 二、准备工作 三、数据准备 四、计算距离 五、筛选最近点 六、完整代码示例 七、性能优化 八、总结 一、引言 在地理信息系统&#xff08;GIS&#xff09;中&#xff0c;经常需要查找两张表中距离最近的点。传统的做法是使用Mapinfo软件&#xff0c;但这…

探索世界,从一款好用的浏览器开始!

好用的浏览器分享 在这个数字化的时代&#xff0c;浏览器已经成为了我们生活中不可或缺的工具。从浏览新闻、社交媒体到工作学习&#xff0c;我们几乎无时无刻不在与浏览器打交道。那么&#xff0c;如何选择一款好用的浏览器呢&#xff1f;今天&#xff0c;我就来为大家分享几…

C++函数指针

目录 背景定义 实例运行结果 背景 定义 函数指针是一个指向函数的指针变量&#xff0c;它可以指向某个函数的入口地址&#xff0c;使得程序可以通过该指针变量调用该函数。 实例 #include <iostream> using namespace std;/*** 函数指针 */// 定义一个函数 int add(i…

SAP ABAP 指针

SAP ABAP 指针 目录 一、FIELD SYMBOL 字段符号 1、定义2、assign分配3.分配-内表4.动态内表 二、数据引用 一、FIELD SYMBOL 字段符号 定义&#xff1a;是已经存在的数据对象的占位符或者符号名称&#xff0c;通过关键字 FIELD-SYMBOLS 定义&#xff0c;类似于指针&#x…

systemverilog/verilog文件操作

1、Verilog文件操作 Verilog具有系统任务和功能,可以打开文件、将值输出到文件、从文件中读取值并加载到其他变量和关闭文件。 1.1 、Verilog文件操作 1.1.1、打开和关闭文件 module tb; // 声明一个变量存储 file handler integer fd; initial begin // 以写权限打开一个文…