【Hello Linux】线程池

news2024/12/28 23:33:55

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步
本篇博客简介:简单介绍linux中线程池概念

线程池

  • Linux线程池
    • 线程池的概念
    • 线程池的优点
    • 线程池的应用场景
    • 线程池实现

Linux线程池

线程池的概念

线程池是一种线程使用模式。

如果我们每次申请一个线程操作系统都要执行一次申请函数 分配空间的话不可避免的会造成效率的降低 所以说为了提升效率我们使用线程池维护多个线程 等待着监督管理者分配可并发执行的任务

线程池的优点

  • 线程池避免了在处理短时间任务时创建与销毁线程的代价
  • 线程池不仅能够保证内核充分利用 还能防止过分调度

线程池的应用场景

  • 需要大量的线程来完成任务 且完成任务的时间比较短
  • 对性能要求苛刻的应用 比如要求服务器迅速响应客户请求
  • 接受突发性的大量请求 但不至于使服务器因此产生大量线程的应用

线程池实现

我们下面使用代码实现一个简单的线程池

在这里插入图片描述

  • 线程池中的线程从任务队列中拿任务并且处理
  • 线程池只暴露给外界一个push接口来添加任务

代码如下

#pragma once     
    
#include <iostream>    
#include <unistd.h>    
#include <queue>    
#include <pthread.h>    
using namespace std;    
    
const int NUM = 5;    
    
template<class T>    
class ThreadPool    
{    
  private:    
    queue<T> _task_queue;    
    int _thread_num;    
    pthread_mutex_t _mutex;    
    pthread_cond_t _cond;    
    
  private:    
    bool IsEmpty()    
    {    
      return _task_queue.size() == 0;    
    }    
    
    bool IsFull()    
    {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
      return _task_queue.size() == _thread_num;    
    }    
    
    void LockQueue()    
    {    
      pthread_mutex_lock(&_mutex);    
    }    
    
    void UnlockQueue()    
    {    
      pthread_mutex_unlock(&_mutex);    
    }    
    
    void Wait()    
    {    
      pthread_cond_wait(&_cond , &_mutex);    
    }    
    
    void WakeUp()    
    {    
      pthread_cond_signal(&_cond);    
    }    
    
  public:    
    ThreadPool(int num = NUM)    
      :_thread_num(num)    
    {    
      pthread_mutex_init(&_mutex);    
      pthread_cond_init(&_cond);    
    }    
    ~ThreadPool()    
    {    
      pthread_mutex_destroy(&_mutex);    
      pthread_mutex_destroy(&_mutex);    
    }    
    
  public:    
    static void* Routine(void* arg)    
    {    
      pthread_detach(pthread_self());    
      auto* self = (ThreadPool*)arg;    
    
      while(true)    
      {    
        self->LockQueue();    
        while(self->IsEmpty())    
        {    
          self->Wait();    
        }    
    
        T task;    
        self->Pop(task);    
        self->UnlockQueue();    
    
        task.run();    
      }    
    }    
    
    void ThreadPoolInit()    
    {    
      pthread_t tid;    
      for(int i = 0; i < _thread_num ; i++)    
      {    
        pthread_create(&tid , nullptr , Routine , this);    
      }    
    }    
    
    void Push(const T& task)    
    {    
      LockQueue();    
      _task_queue.push(task);    
      UnlockQueue();    
      WakeUp();    
    }    
    
    void Pop(T& task)    
    {    
      task = _task_queue.front();    
      _task_queue.pop();    
    }    
};    

为什么要有互斥锁和变量

线程池中的任务队列是会被多个进程访问的临界资源 所以说我们需要引入互斥锁来实现互斥

线程池当中的线程要从任务队列里拿任务 前提条件是任务队列中必须要有任务

因此线程池当中的线程在拿任务之前 需要先判断任务队列当中是否有任务 若此时任务队列为空 那么该线程应该进行等待 直到任务队列中有任务时再将其唤醒 因此我们需要引入条件变量

当外部线程向任务队列中Push一个任务后 此时可能有线程正处于等待状态 因此在新增任务后需要唤醒在条件变量下等待的线程

当外部线程向任务队列中Push一个任务后 此时可能有线程正处于等待状态 因此在新增任务后需要唤醒在条件变量下等待的线程

注意:

  1. 当某线程被唤醒时 其可能是被异常或是伪唤醒 或者是一些广播类的唤醒线程操作而导致所有线程被唤醒 使得在被唤醒的若干线程中 只有个别线程能拿到任务 此时应该让被唤醒的线程再次判断是否满足被唤醒条件 所以在判断任务队列是否为空时 应该使用while进行判断 而不是if
  2. pthread_cond_broadcast函数的作用是唤醒条件变量下的所有线程 而外部可能只Push了一个任务 我们却把全部在等待的线程都唤醒了 此时这些线程就都会去任务队列获取任务 但最终只有一个线程能得到任务 所以说我们只需要使用pthread_cond_signal函数唤醒一个进程就行
  3. 当线程从任务队列中拿到任务后 该任务就已经属于当前线程了 与其他线程已经没有关系了 因此应该在解锁之后再进行处理任务 而不是在解锁之前进行 因为处理任务的过程可能会耗费一定的时间 所以我们不要将其放到临界区当中
  4. 如果将处理任务的过程放到临界区当中 那么当某一线程从任务队列拿到任务后 其他线程还需要等待该线程将任务处理完后 才有机会进入临界区 此时虽然是线程池 但最终我们可能并没有让多线程并行的执行起来

为什么Routine要设置为静态方法

使用pthread_create函数创建线程时 需要为创建的线程传入一个Routine(执行例程) 该Routine只有一个参数类型为void的参数以及返回类型为void的返回值

但是我们说过作为如果作为类的成员函数它是会有一个隐藏的参数 this指针 所以说如果我们不设置静态方法的话 就没有办法传递这两个参数 所以说要设置静态

那么如果我们不设置静态 使用全局函数呢?

如果我们使用全局函数的话 我们就没办法传递tihs指针进去 从而导致函数内部无法调用Pop函数等类内部函数

此外将线程运行函数暴露为全局函数的话也有被其他人调用的风险 所以综合考虑来说 我们应该使用static设置静态成员

任务类型的设计

们将线程池进行了模板化 因此线程池当中存储的任务类型可以是任意的 但无论该任务是什么类型的 在该任务类当中都必须包含一个Run方法 当我们处理该类型的任务时只需调用该Run方法即可

我们在下面实现一个计算机方法

#pragma once

#include <iostream>

//任务类
class Task
{
public:
	Task(int x = 0, int y = 0, char op = 0)
		: _x(x), _y(y), _op(op)
	{}
	~Task()
	{}

	//处理任务的方法
	void Run()
	{
		int result = 0;
		switch (_op)
		{
		case '+':
			result = _x + _y;
			break;
		case '-':
			result = _x - _y;
			break;
		case '*':
			result = _x * _y;
			break;
		case '/':
			if (_y == 0){
				std::cerr << "Error: div zero!" << std::endl;
				return;
			}
			else{
				result = _x / _y;
			}
			break;
		case '%':
			if (_y == 0){
				std::cerr << "Error: mod zero!" << std::endl;
				return;
			}
			else{
				result = _x % _y;
			}
			break;
		default:
			std::cerr << "operation error!" << std::endl;
			return;
		}
		std::cout << "thread[" << pthread_self() << "]:" << _x << _op << _y << "=" << result << std::endl;
	}
private:
	int _x;
	int _y;
	char _op;
};

主线程

主线程就负责不断向任务队列当中Push任务就行了 此后线程池当中的线程会从任务队列当中获取到这些任务并进行处理

#include "Task.hpp"
#include "ThreadPool.hpp"

int main()
{
	srand((unsigned int)time(nullptr));
	ThreadPool<Task>* tp = new ThreadPool<Task>; //线程池
	tp->ThreadPoolInit(); //初始化线程池当中的线程
	const char* op = "+-*/%";
	//不断往任务队列塞计算任务
	while (true){
		sleep(1);
		int x = rand() % 100;
		int y = rand() % 100;
		int index = rand() % 5;
		Task task(x, y, op[index]);
		tp->Push(task);
	}
	return 0;
}

我们在运行代码之后便会出现下面的情况

在这里插入图片描述

此后我们如果想让线程池处理其他不同的任务请求时 我们只需要提供一个任务类 在该任务类当中提供对应的任务处理方法就行了

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

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

相关文章

PyTorch深度学习实战 | 高斯混合模型聚类原理分析

01、问题描述 为理解高斯混合模型解决聚类问题的原理&#xff0c;本实例采用三个一元高斯函数混合构成原始数据&#xff0c;再采用GMM来聚类。 1) 数据 三个一元高斯组件函数可以采用均值和协方差表示如表1所示&#xff1a; ▍表1 三个一元高斯组件函数的均值和协方差 每个高斯…

git的使用——操作流程

一、什么是git git是一个开源的分布式版本控制软件&#xff0c;能够有效并高效的处理很小到非常大的项目。 二、添加SSH公钥 安装下载后&#xff0c;会发现鼠标右击&#xff0c;会出现 Git Bash Here 这个选项&#xff0c;如图所示&#xff0c;点击进入 1.打开git窗口后&…

018:Mapbox GL加载Google地图(影像瓦片图)

第018个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载google地图。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共80行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:https://xia…

鉴智机器人重磅发布双目智驾解决方案,新一代全系智驾产品线亮相上海车展

4月18日&#xff0c;以「拥抱汽车行业新时代」为主题的2023上海车展正式拉开帷幕。以视觉3D理解为核心的下一代自动驾驶系统提供商鉴智机器人&#xff0c;携全新升级的智驾产品线首次亮相车展&#xff0c;重磅发布基于AI的双目立体视觉智驾方案。 凭借双目立体视觉系统的差异化…

智能洗地机好用吗?值得入手的洗地机推荐

洗地机是一款高效的地面清洁设备&#xff0c;不仅可以很好清理地面不同形态的干湿垃圾&#xff0c;还减少了人工和水资源的浪费&#xff0c;是我们日常生活中必不可少的清洁工具。作为以一位评测博主&#xff0c;很多朋友咨询我在选购洗地机时应该注意哪些要点&#xff0c;有哪…

记一次生产要我狗命的问题

问题起因&#xff1a;引入disruptor框架 简单理解就是生产消费者模式 用来支持高并发 先说问题和改正 再展开 问题&#xff1a;没有当时的截图了 直接描述吧 问题就是cpu占用过高 居高不下的那种 排查&#xff1a;就是看线程名字和占用的大概 再根据近期发布的东西 再根据本地…

学系统集成项目管理工程师(中项)系列08b_合同管理(下)

1. 项目变更约定 1.1. 合同生效后&#xff0c;当事人不得因姓名、名称的变更或者法定代表人、负责人、承办人的变动而不履行合同义务 2. 违约责任的承担方式 2.1. 继续履行 2.2. 采取补救措施 2.3. 赔偿损失 2.4. 支付约定违约金或定金 3. 注意事项 3.1. 当事人的法律资…

Linux FTP服务

FTP服务 作用 传输文件 端口 FTP服务器默认使用TCP协议的20、21端口与客户端进行通信 20端口用于建立数据连接&#xff0c;并传输文件数据 21端口用于建立控制连接&#xff0c;并传输FTP控制命令 模式 FTP数据连接分为主动模式和被动模式 主动模式&#xff1a;客户端告诉服务端…

电路原理-反激式电路

1、1反激式电路是小功率电源(150W以下)当中&#xff0c;最常用的电路&#xff0c;它的工作原理如下。 1、2如图1&#xff0c;变压器T1&#xff0c;标记红点的端&#xff0c;12、3、A为同名端&#xff0c;10、1、B为异名端。 当MOS管导通的时候&#xff0c;初级绕组N1、…

瑞吉外卖LinuxRedis

1、linux简介 Linux系统版本 Linux系统分为内核版和发行版 内核版&#xff1a; 由LinusTorvalds及其团队开发、维护 免费、开源 负责控制硬件 发行版&#xff1a; 基于Linux内核版进行扩展 由各个Linux厂商开发、维护 有收费…

C++类的理解与类型名,类的成员,两种定义方式,类的访问限定符,成员访问,作用域与实例化对象

面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成 面向…

ETCD(三)操作指令

1. put put #将给定的key写入到存储 --ignore-lease[false] #使用当前租约更新key --ignore-value[false] #使用当前值更新key --lease"0" # 要附加到key的租约ID&#xff08;十六进制&#xff09; --prev-kv[false] # 返回修改前的上一个键值对2. get get #获取给…

无线洗地机哪款性价比高?高性价比的洗地机分享

虽说现在市面上清洁工具很多&#xff0c;但是要说清洁效果最好的&#xff0c;肯定非洗地机莫属。它集合了吸&#xff0c;洗&#xff0c;拖三大功能&#xff0c;干湿垃圾一次清理&#xff0c;还能根据地面的脏污程度进行清洁&#xff0c;达到极致的清洁效果&#xff0c;省时省力…

4月21日第壹简报,星期五,农历三月初二

4月21日第壹简报&#xff0c;星期五&#xff0c;农历三月初二坚持阅读&#xff0c;静待花开1. 推特拒向大模型免费开放数据&#xff01;马斯克威胁起诉微软&#xff1b;Reddit宣布不再向大模型免费开放数据&#xff0c;要求科技巨头付费使用API接口。2. 浙江&#xff1a;鼓励杭…

【JUC】Java并发机制的底层实现原理

【JUC】Java并发机制的底层实现原理 参考资料&#xff1a; CPU 缓存一致性 《Java并发编程的艺术》 【JUC并发编程】CAS到底加不加锁&#xff1f; 如何写出让 CPU 跑得更快的代码&#xff1f; 彻底理解Java并发编程之Synchronized关键字实现原理剖析 【JUC并发编程】Synchroni…

线程安全版本的单例设计模式 与 生产者消费者模型简介

目录 单例设计模式 单例设计模式——饿汉式 单例设计模式——懒汉式 单例设计模式——懒汉式&#xff08;优化步骤&#xff09; 生产者消费者模型 介绍 优点 补充&#xff1a;关于阻塞队列 单例设计模式 单例设计模式能够保证某个类的实例在程序运行过程中始终都只会存…

代码随想录Day57

1143.最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字…

Cesium 实战-最新版(1.104.0)通过异步方式初始化地球,加载影像以及高程图层

Cesium 实战-最新版&#xff08;1.104.0&#xff09;通过异步方式初始化地球&#xff0c;加载影像以及高程图层 遇到问题初始化底图初始化高程&#xff08;监听载入完成事件&#xff0c;开启关闭高程&#xff09;初始化 3dtile Cesium 最新版&#xff08;1.104.0&#xff09;变…

2021地理设计组二等奖:基于地理大数据的南昌中心城区空间功能感知与分区

一、设计背景及意义 随着经济快速发展&#xff0c;城市功能类型也越来越多&#xff0c;在空间上逐渐聚集和演化&#xff0c;形成了居住区&#xff0c;商业区等城市功能区&#xff0c;而这些功能区没有明显边界&#xff0c;确定困难&#xff0c;如使用传统人力调查方法费时费力…

PHP快速入门16-用curl发起POST和GET的请求

文章目录 前言curl介绍发送GET请求发送POST请求其他选项 总结 前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP快速入门与实战 在Web开发中&#xff0c;经常需要与其他服务器进行数据交互。而现在&#xff0c;绝大多数的接口都是基于HTTP协议的&#xff0c;因此我们需要学会…