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

news2024/11/28 12:43:26

一、线程池的模型

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

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

相关文章

MySQL主从集群

MySQL主从集群 主从模式、集群模式&#xff0c;都是在一个项目中使用多个mysql节点进行存储和读取数据。 当单机模式部署&#xff0c;不满足安全性、高可用、高并发等需求的时候&#xff0c;就需要考虑主从模式或者集群模式部署。 什么是主从模式&#xff1f; 主从模式&…

commvault学习(5):在linux上安装cv客户端

我的环境&#xff1a; 服务器&#xff08;同时装有CS、MA&#xff09;&#xff1a;windows server2008r2 客户端&#xff1a;两台centos7 1.为两台centos7配置静态ip 使得2者可以与服务器ping通 2.在两台centos7上预留出足够大的磁盘空间以存放安装文件 我是在/mnt下创建了…

十本你不容错过的Docker入门到精通书籍推荐

前言&#xff1a; 最近有许多小伙伴私信让我推荐几本关于Docker学习的书籍&#xff0c;今天花了一下午的时间在网上查阅了一些资料和结合自己平时工作中的一些学习参考资料书籍写下了这篇文章。注意以下书籍都是十分优秀的Docker学习书籍&#xff08;因此排名不分先后&#xff…

tritonserver学习之三:tritonserver运行流程

tritonserver学习之一&#xff1a;triton使用流程 tritonserver学习之二&#xff1a;tritonserver编译 tritonserver学习之四&#xff1a;命令行解析 1、triton启动运行流程 triton功能设计全面&#xff0c;而且复杂&#xff0c;下面是triton(2.41.0)启动的整个流程&#x…

从函数角度看品牌网络推广:短期与长期的博弈

在数字营销的世界里&#xff0c;品牌网络推广无疑是一个复杂而多维度的领域。它不仅仅是一个简单的“投入-产出”关系&#xff0c;而是一个涉及到多种因素、时间和空间的动态过程。当我们尝试从函数的角度去解读品牌网络推广时&#xff0c;会发现它其实是一个不断变化的函数关系…

我在人工智能技术方面的发展规划

人工智能(AI)是当今科技领域最具前景和影响力的技术之一,它已经渗透到各行各业,为社会和经济发展带来了巨大的机遇和挑战。作为一名从事人工智能研究和开发的专业人士,我有必要制定一个合理的人工智能技术发展规划,以指导我的学习和工作,提高我的专业水平和竞争力,为人…

009 Linux_文件系统 | 软硬链接

前言 本文将会向你介绍文件系统与软硬链接 文章重点 本文将会先向你介绍文件是如何在磁盘上进行管理的&#xff0c;关于文件的管理将会从管理属性和管理内容两方面来谈&#xff0c;最后会向你介绍软硬链接的概念 文件在磁盘中的管理 首先&#xff0c;假设一个磁盘200GB&#…

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…

25计算机考研408专业课复习计划

点击蓝字&#xff0c;关注我们 今天要分享的是25计算机考研408专业课复习计划。 以下内容供大家参考&#xff0c;大家要根据自己的复习情况进行适当调整。 统考与自命题 统考科目是指计算机学科专业基础综合&#xff08;408&#xff09;&#xff0c;满分150分&#xff0c;试…

用MATLAB函数在图表中建立模型

本节介绍如何使用Stateflow图表创建模型&#xff0c;该图表调用两个MATLAB函数meanstats和stdevstats。meanstats计算平均值&#xff0c;stdevstats计算vals中值的标准偏差&#xff0c;并将它们分别输出到Stateflow数据平均值和stdev。 请遵循以下步骤&#xff1a; 1.使用以下…

医保移动支付加密解密请求工具封装【国密SM2SM4】

文章目录 医保移动支付加密解密请求工具封装一、项目背景二、使用方法三、接口调用四、源码介绍五、下载地址 医保移动支付加密解密请求工具封装 定点医药机构向地方移动支付中心发起费用明细上传、支付下单、医保退费等交易时需要发送密文&#xff0c;由于各大医疗机构厂商的开…

揭秘AI换脸技术:从原理到应用

随着人工智能技术的不断发展&#xff0c;AI换脸技术逐渐成为人们关注的焦点。这项神奇的技术能够将一张图像或视频中的人脸替换成另一张人脸&#xff0c;让人不禁惊叹科技的神奇。那么&#xff0c;AI换脸技术究竟是如何实现的呢&#xff1f;本文将带您深入了解AI换脸技术的原理…

python系列-输入输出关系运算符算术运算符

&#x1f308;个人主页: 会编程的果子君​&#x1f4ab;个人格言:“成为自己未来的主人~” 目录 注释的语法 注释的规范 输入输出 通过控制台输出 通过控制台输入 运算符 算术运算符 关系运算符 注释的语法 python中有两种注释风格&#xff1a; 1.注释行&#xff1a;…

无人机打击激光器

激光器的应用非常广泛&#xff0c;涵盖了多个领域。以下是一些主要的激光器应用&#xff1a; 医疗领域&#xff1a;激光器在医疗行业中有着重要应用&#xff0c;比如用于激光手术&#xff08;如眼科手术&#xff09;、皮肤治疗、牙科治疗、肿瘤治疗等。 工业制造&#xff1a;在…

(菜鸟自学)初学脚本编程

&#xff08;菜鸟自学&#xff09;初学脚本编程 Bash脚本概述编写一个测试在线主机的脚本程序 Python脚本概述编写一个与Netcat功能类似的脚本程序 C语言脚本概述编写C语言脚本程序&#xff08;Hello World&#xff09; Bash脚本概述 Bash脚本是一种基于Bash&#xff08;Bourn…

图片批量建码怎么用?每张图片快速生成二维码

当我们需要给每个人分别下发对应的个人证件类图片信息&#xff0c;比如制作工牌、荣誉展示或者负责人信息展示时&#xff0c;现在都开始使用二维码的方法来展示员工信息。那么如何快速将每个人员的信息图片分别制作成二维码图片呢&#xff0c;最简单的方法就是使用图片批量建码…

vue中内置指令v-model的作用和常见使用方法介绍以及在自定义组件上支持

文章目录 一、v-model是什么二、什么是语法糖三、v-model常见的用法1、对于输入框&#xff08;input&#xff09;&#xff1a;2、对于复选框&#xff08;checkbox&#xff09;&#xff1a;3、对于选择框&#xff08;select&#xff09;&#xff1a;4、对于组件&#xff08;comp…

群发邮件效果追踪:掌握数据,优化营销策略

我们在邮件群发结束后&#xff0c;如果想要了解到这次群发活动的效果怎么样&#xff0c;就需要通过一些数据。比如说邮件达到率、打开率、跳出率、退订率等。这些信息可以将收件人的行为数据化&#xff0c;让我们可以更清晰地对活动进行深入分析让我们及时地找出问题和优点&…

C语言数据结构——顺序表

&#xff08;图片由AI生成&#xff09; 0.前言 在程序设计的世界里&#xff0c;数据结构是非常重要的基础概念。本文将专注于C语言中的一种基本数据结构——顺序表。我们将从数据结构的基本概念讲起&#xff0c;逐步深入到顺序表的内部结构、分类&#xff0c;最后通过一个实…

网络安全:守护数字世界的盾牌

在当今数字化的时代&#xff0c;网络已经渗透到我们生活的方方面面。从社交媒体到在线银行&#xff0c;从在线购物到工作文件传输&#xff0c;网络几乎无处不在。然而&#xff0c;随着网络的普及&#xff0c;网络安全问题也日益凸显。那么&#xff0c;如何确保我们的数字资产安…