【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

这篇具有很好参考价值的文章主要介绍了【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、线程池介绍

💻线程池基本概念

💻线程池组成部分

💻线程池工作原理 

二、线程池代码封装

🌈main.cpp

🌈ThreadPool.h

🌈ThreadPool.cpp

🌈ChildTask.h 

🌈ChildTask.cpp

🌈BaseTask.h

🌈BaseTask.cpp

三、测试效果

四、总结

📌创建线程池的好处


前言

本文主要学习Linux内核编程,结合Visual Studio 2019进行跨平台编程,内容包括线程池介绍以及线程池封装

一、线程池介绍

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu

💻线程池基本概念

  • 线程池是预先创建线程的一种技术 (服务器真正意义上实现高并发就必须用线程池)
  • 🌰举个例子:生活中的水池,是装东西的容器,用来装水的,线程池当然就是拿来装线程的
  • 线程池在任务还没有到来之前,创建一定数量的线程,放入空闲队列中,这些线程都是处于阻塞状态,不消耗CPU,但占用较小的内存空间
  • 当新任务到来时,缓冲池选择一个空闲线程,把任务传入此线程中运行,如果缓冲池已经没有空闲线程,则新建若干个线程,当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源

💻线程池组成部分

  • 线程池类

        维护工作者线程队列(包括空闲与忙碌队列)

        维护一个任务队列

        维护一个线程池调度器指针

  • 线程池调度器(本身也是一个线程)

        负责线程调度

        负责任务分配

  • 工作者线程类(线程池中的线程类的封装)
  • 任务队列
  • 任务接口(实际的业务逻辑都继承自该接口)

💻线程池工作原理 

根据服务器的需要,来设置线程的数量,可能是10条、20条、30条,根据服务器的承载,10条不代表只能做10个任务,总有任务做的快,有的做的慢,可能可以完成20个任务 

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu

🌰举个例子:如上动图所示,当我们服务器收到一个注册业务,是一个服务器要执行的任务,它会进入到任务队列,队列先进先出,顺次执行,任务会唤醒空闲列表当中的一个空闲的线程,接到任务之后,空闲线程会从空闲列表中消失,进入到忙碌列表,去完成对应的任务,完成任务后,从忙碌列表中出去,到空闲列表继续等待新任务

如果,所有的线程都在忙,都在做任务,这时候登录进来,先进入任务队列,会创建一个新的线程来接这个任务,当所有线程都完成任务,回到空闲列表后,新创建的线程销毁,留下原先设置的对应数量线程(类似,保留老员工,把实习生裁员)

  • 队列:先进先出
  • 空闲列表(链表):不定长(有的时候可能需要创建新线程来接任务)
  • 忙碌列表(链表):不定长(有的时候可能需要创建新线程来接任务)

话不多说,咱们上号,封装一下线程池相关函数,来进行测试 

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu

二、线程池代码封装

🌈main.cpp

  • 主函数,设置10条线程,来执行30个任务 
#include <iostream>
#include <stdio.h>
#include "ThreadPool.h"
#include "ChildTask.h"
using namespace std;

int main()
{
	ThreadPool* pool = new ThreadPool(10);//10条线程

	for (int i = 0; i < 30; i++)//设置30个任务
	{
		char buf[40] = { 0 };//初始化
		sprintf(buf, "%s%d", "任务", i);

		BaseTask* task = new ChildTask(buf);
		pool->pushTask(task);
	}

	while (1) {}

	return 0;
}

🌈ThreadPool.h

  • 对线程池进行设计,核心包括最大、最小线程数忙碌列表空闲列表任务队列互斥量条件变量,以及线程执行函数
#pragma once
#include <queue>//队列
#include <list>//链表头文件
#include <pthread.h>//线程头文件
#include <algorithm>//find查找
#include <iostream>
#include "BaseTask.h"

using namespace std;

#define MIN_NUM 10//最小值 默认参数

class ThreadPool
{
public:
	ThreadPool(const int num = MIN_NUM);
	~ThreadPool();

	//判断任务队列是否为空
	bool QueueIsEmpty();
	//线程互斥量加锁解锁
	void Lock();
	void Unlock();
	//线程条件变量等待和唤醒
	void Wait();
	void WakeUp();

	//添加任务到任务队列
	void pushTask(BaseTask* task);
	//从任务队列移除任务
	BaseTask* popTask(BaseTask* task);

	//从忙碌回到空闲 工作结束
	void MoveToIdle(pthread_t id);

	//从空闲到忙碌 工作开始
	void MoveToBusy(pthread_t id);

	//线程执行函数
	static void* RunTime(void* vo);

private:
	int threadMinNum;//最大线程数量
	int threadMaxNum;//最小线程数量
	queue<BaseTask*>taskQueue;//任务队列
	list<pthread_t>busyList;//线程忙碌列表
	list<pthread_t>idleList;//线程空闲列表
	pthread_mutex_t mutex;//互斥量:做锁
	pthread_cond_t cond;//条件变量:让线程等待或者唤醒
};

🌈ThreadPool.cpp

  • 对函数进行参数的设置,核心在于线程执行函数上的设置,在工作前和工作完设置打印,方便我们进行观察
#include "ThreadPool.h"

ThreadPool::ThreadPool(const int num)
{
	this->threadMinNum = num;

	//条件变量、互斥量初始化
	pthread_mutex_init(&this->mutex, NULL);
	pthread_cond_init(&this->cond, NULL);

	pthread_t id;
	//线程num条创建
	for (int i = 0; i < this->threadMinNum; i++)
	{
		//线程创建
		pthread_create(&id, NULL, RunTime, this);
		this->idleList.push_back(id);//线程存入空闲列表
	}
}

ThreadPool::~ThreadPool()
{
}
//任务队列是否为空
bool ThreadPool::QueueIsEmpty()
{
	return this->taskQueue.empty();
}
//线程加锁
void ThreadPool::Lock()
{
	pthread_mutex_lock(&this->mutex);
}
//线程解锁
void ThreadPool::Unlock()
{
	pthread_mutex_unlock(&this->mutex);
}
//线程等待
void ThreadPool::Wait()
{
	pthread_cond_wait(&this->cond, &this->mutex);
}
//线程唤醒
void ThreadPool::WakeUp()
{
	pthread_cond_signal(&this->cond);
}
//添加任务到任务队列
void ThreadPool::pushTask(BaseTask* task)
{
	Lock();
	taskQueue.push(task);
	Unlock();
	WakeUp();
}
//从任务队列移除任务
BaseTask* ThreadPool::popTask(BaseTask* task)
{
	task = this->taskQueue.front();//从队列头取
	this->taskQueue.pop();//删除队列头

	return task;
}
//从忙碌回到空闲 工作结束
void ThreadPool::MoveToIdle(pthread_t id)
{
	list<pthread_t>::iterator iter;
	iter = find(busyList.begin(), busyList.end(), id);

	if (iter != busyList.end())
	{
		//从忙碌移除
		this->busyList.erase(iter);
		//添加到空闲
		this->idleList.push_back(*iter);//this->idleList.push_back(id)
	}
}
//从空闲到忙碌 工作开始
void ThreadPool::MoveToBusy(pthread_t id)
{
	list<pthread_t>::iterator iter;
	iter = find(idleList.begin(), idleList.end(), id);

	if (iter != idleList.end())
	{
		//从空闲移除
		this->idleList.erase(iter);
		//添加到忙碌
		this->busyList.push_back(*iter);//this->idleList.push_back(id)
	}
}
//线程执行函数
void* ThreadPool::RunTime(void* vo)
{
	//拿到执行线程自己的id 因为后面要处理忙碌和空闲的情况
	pthread_t id = pthread_self();
	//确保主线程与子线程分离,子线程结束后,资源自动回收
	pthread_detach(id);
	//线程参数获取
	ThreadPool* argThis = (ThreadPool*)vo;

	while (true)
	{
		argThis->Lock();
		//如果任务队列为空 线程则一直等待 
		//知道任务队列不为空则会被pushTask函数唤醒线程
		while (argThis->QueueIsEmpty())
		{
			argThis->Wait();
		}
		argThis->MoveToBusy(id);

		cout << "工作前  任务数:" << argThis->taskQueue.size() << endl;
		cout << "工作前  busy:" << argThis->busyList.size() << endl;
		cout << "工作前  idle:" << argThis->idleList.size() << endl;
		cout << "-----------------------------------------------" << endl;
		//取任务
		BaseTask* task;
		task = argThis->popTask(task);

		argThis->Unlock();

		//任务工作
		task->working();

		//工作结束
		argThis->Lock();
		argThis->MoveToIdle(id);
		argThis->Unlock();

		cout << "工作完  任务数:" << argThis->taskQueue.size() << endl;
		cout << "工作完  busy:" << argThis->busyList.size() << endl;
		cout << "工作完  idle:" << argThis->idleList.size() << endl;
		cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;
	}

	return nullptr;
}

🌈ChildTask.h 

  • 子类配置 
#pragma once
#include "BaseTask.h"
#include <iostream>
#include <unistd.h>//sleep头文件
using namespace std;

class ChildTask :
	public BaseTask
{
public:
	ChildTask(char* data);
	~ChildTask();
	void working();
};

🌈ChildTask.cpp

  • 子类设置延时模拟做任务的时间比较长 
#include "ChildTask.h"

ChildTask::ChildTask(char* data) :BaseTask(data)//参数传给父类
{
}

ChildTask::~ChildTask()
{
}

void ChildTask::working()
{
	cout << this->data << "正在执行......" << endl;
	sleep(3);//延时3秒 (模拟做业务的时间比较长)
}

🌈BaseTask.h

  • 基类设置结构体来装业务 
#pragma once
#include <string.h>
class BaseTask
{
public:
	BaseTask(char* data);
	~BaseTask();
	char data[1024]; //装业务
	virtual void working() = 0;//虚函数
};

🌈BaseTask.cpp

  • 基类配置 
#include "BaseTask.h"

BaseTask::BaseTask(char* data)
{
	bzero(this->data, sizeof(this->data));//清空
	memcpy(this->data, data, sizeof(data));
}

BaseTask::~BaseTask()
{
	delete this->data;//清除释放
}

三、测试效果

  • 通过Linux连接VS进行跨平台编程,我们可以清晰的看到有几个线程是在做任务,几个线程是空闲的,整个过程就很清晰直观的展现出来了,如下动图所示: 

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu

  • 10条线程做30个任务的全部记录,如下如所示:

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu

四、总结

📌创建线程池的好处

  • 线程池的使用,能让我们搭建的高并发服务器真正意义上做到高并发
  • 降低资源消耗

        通过重复利用自己创建的线程降低线程创建和销毁造成的消耗

  • 提高响应速度

        当任务到达时,任务可以不需要等待线程创建和销毁就能立即执行

  • 提高线程的可管理性

        线程式稀缺资源,如果无限的创建线程,不仅会消耗资源,还会降低系统的稳定性

        使用线程池可以进行统一分配,调优和监控

以上就是本文的全部内容啦!如果对您有帮助,麻烦点赞啦!收藏啦!欢迎各位评论区留言!!!

服务器用线程池,Linux网络编程,linux,网络,tcp/ip,c++,ubuntu文章来源地址https://www.toymoban.com/news/detail-793425.html

到了这里,关于【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux网络编程】TCP并发服务器的实现(IO多路复用select)

    服务器模型主要分为两种, 循环服务器 和 并发服务器 。 循环服务器 : 在同一时间只能处理一个客户端的请求。 并发服务器 : 在同一时间内能同时处理多个客户端的请求。 TCP的服务器默认的就是一个循环服务器,原因是有两个阻塞 accept函数 和recv函数 之间会相互影响。

    2024年02月03日
    浏览(81)
  • 【网络编程】高性能并发服务器源码剖析

      hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中, 你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!! 希望这篇文章能

    2024年04月15日
    浏览(56)
  • 网络编程(8.14)TCP并发服务器模型

    作业: 1. 多线程中的newfd,能否修改成全局,不行,为什么? 2. 多线程中分支线程的newfd能否不另存,直接用指针间接访问主线程中的newfd,不行,为什么? 多线程并发服务器模型原代码: 1.将newfd改成全局变量效果:  答:不行,因为newfd是全局变量的话,客户端连接后生成

    2024年02月13日
    浏览(49)
  • 计算机网络编程 | 并发服务器代码实现(多进程/多线程)

    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。 专栏:《网络编程》 当涉及到构建高性能的服务

    2024年02月08日
    浏览(80)
  • libevent高并发网络编程 - 04_libevent实现http服务器

    链接: C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 在libevent中,HTTP的实现主要是通过 evhttp 模块来完成的。 evhttp 提供了一个高层次的HTTP服务器接口,可以处理HTTP请求并发送HTTP响应。 在源码中,libevent的HTTP协议处理主要是通过 evhttp 模块来完成的。

    2024年02月15日
    浏览(36)
  • 多进程并发TCP服务器模型(含客户端)(网络编程 C语言实现)

    摘要 :大家都知道不同pc间的通信需要用到套接字sockte来实现,但是服务器一次只能收到一个客户端发来的消息,所以为了能让服务器可以接收多个客户端的连接与消息的传递,我们就引入了多进程并发这样一个概念。听名字就可以知道--需要用到进程,当然也有多线程并发

    2024年02月17日
    浏览(66)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(79)
  • 计算机网络套接字编程实验-TCP多进程并发服务器程序与单进程客户端程序(简单回声)

    1.实验系列 ·Linux NAP-Linux网络应用编程系列 2.实验目的 ·理解多进程(Multiprocess)相关基本概念,理解父子进程之间的关系与差异,熟练掌握基于fork()的多进程编程模式; ·理解僵尸进程产生原理,能基于|sigaction()或signal(),使用waitpid()规避僵尸进程产生; ·

    2024年02月12日
    浏览(46)
  • Linux高性能服务器编程 学习笔记 第五章 Linux网络编程基础API

    我们将从以下3方面讨论Linux网络API: 1.socket地址API。socket最开始的含义是一个IP地址和端口对(ip,port),它唯一表示了使用TCP通信的一端,本书称其为socket地址。 2.socket基础API。socket的主要API都定义在sys/socket.h头文件中,包括创建socket、命名socket、监听socket、接受连接、发

    2024年02月07日
    浏览(55)
  • Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

    文章目录: 一:定义和流程分析 1.定义 2.流程分析  3.网络字节序 二:相关函数  IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序) socket函数(创建一个套接字) bind函数(给socket绑定一个服务器地址结构(IP+port)) listen函数(设置最大连接数或者说能同时进行三次握手的最

    2024年02月12日
    浏览(84)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包