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

一、线程池的模型

在学习过相关的多线程知识后,从更抽象的角度来看待多线程解决问题的方向,其实仍然是典型的生产和消费者的模型。无论是数据计算、存储、分发和任务处理等都是通过多线程这种手段来解决生产者和消费者不匹配的情况。所以,得到的结论就清晰了,生产者和消费者根本不匹配的情况下,包括多线程编程在内的各种手段都是无法解决实际问题的。这一点很简单,可是在实际编程过程中,仍然有很多人专注于细节和业务,忽视了这个问题。
模型的意义在于,在看似不匹配的的场景下,通过模型优化实现匹配。这句话有点绕,举个例子就明白了。比如组装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.hqwc.cn/news/418421.html

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

相关文章

github经常登不上去怎么办?

问题 想少些代码&#xff0c;多学习&#xff0c;少不了使用github&#xff0c;但是在国内经常上不去&#xff0c;很耽误事&#xff0c;这里提供一个简单方法&#xff0c;供参考。 github GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;可以让开发者共同协作开发软…

softmax回实战

1.数据集 MNIST数据集 (LeCun et al., 1998) 是图像分类中广泛使用的数据集之一&#xff0c;但作为基准数据集过于简单。 我们将使用类似但更复杂的Fashion-MNIST数据集 (Xiao et al., 2017)。 import torch import torchvision from torch.utils import data from torchvisi…

Web自动化测试 —— cookie复用

一、cookie简介 cookie是一些数据&#xff0c;存储于用户电脑的文本文件中 当web服务器想浏览器发送web页面时&#xff0c;在链接关闭后&#xff0c;服务端不会记录用户信息 二、为什么要使用Cookie自动化登录 复用浏览器仍然在每次用例开始都需要人为介入若用例需要经常执行&…

浅析性能测试策略及适用场景

前言 面对日益复杂的业务场景和不同的系统架构&#xff0c;前期的需求分析和准备工作&#xff0c;需要耗费很多的时间。而不同的测试策略&#xff0c;也对我们的测试结果是否符合预期目标至关重要。 这篇博客&#xff0c;聊聊我个人对常见的性能测试策略的理解&#xff0c;以…

Android Matrix绘制PaintDrawable设置BitmapShader,手指触点为圆心scale放大原图,Kotlin(二)

Android Matrix绘制PaintDrawable设置BitmapShader&#xff0c;手指触点为圆心scale放大原图&#xff0c;Kotlin&#xff08;二&#xff09; 在 Android Matrix绘制PaintDrawable设置BitmapShader&#xff0c;手指触点为圆心scale放大原图&#xff0c;Kotlin-CSDN博客 基础上&…

信息登记小程序怎么做_重塑用户互动,开启全新营销篇章

信息登记小程序&#xff1a;重塑用户互动&#xff0c;开启全新营销篇章 在数字化浪潮中&#xff0c;小程序以其便捷、高效的特点&#xff0c;逐渐成为企业与用户之间沟通的桥梁。其中&#xff0c;信息登记小程序更是凭借其独特的定位&#xff0c;在众多小程序中脱颖而出。本文…

如何用GPT进行论文润色与改写?

详情点击链接&#xff1a;如何用GPT进行论文润色与改写&#xff1f; 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Claude2二定…

C#,入门教程(20)——列表(List)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(19)——循环语句&#xff08;for&#xff0c;while&#xff0c;foreach&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/124060844 List顾名思义就是数据列表&#xff0c;区别于数据数组&#xff08;arr…

html5实现好看的年会邀请函源码模板

文章目录 1.设计来源1.1 邀请函主界面1.2 诚挚邀请界面1.3 关于我们界面1.4 董事长致词界面1.5 公司合作方界面1.6 活动流程界面1.7 加盟支持界面1.8 加盟流程界面1.9 加盟申请界面1.10 活动信息界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者&#xff1a;xcLei…

最长公共前缀(Leetcode14)

例题&#xff1a; 分析&#xff1a; 我们可以先定义两个变量 i &#xff0c; j&#xff0c; j表示数组中的每一个字符串&#xff0c; i 表示每个字符串中的第几个字符。一列一列地进行比较&#xff0c;先比较第一列的字符&#xff0c;若都相同&#xff0c;则 i &#xff0c;继…

linux-部署Samba文件共享服务

linux-部署Samba文件共享服务 1、使用命令安装samba服务和samba客户端 dnf install samba samba-client # 或者 yum install samba samba-client2、配置文件的设置(可提前备份smb.conf) vim /etc/samba/smb.conf [global]workgroup SAMBAsecurity userpassdb backend tdbsam…

算法总结——单调栈

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 文章目录 一、单调栈的定义二、单调栈的应用&#xff1a;寻找左边第一个比它小的数单调栈的思想&#xff08;重点&#xff09;&#xff1a;寻找左边第一个比它小的数的下…