Linux —— 线程池

2023-05-16

目录

一、什么是线程池

二、线程池的优点

三、线程池的应用

四、实现一个简单的线程池

五、单例模式

1. 饿汉实现方式

2. 懒汉实现方式

3. 单例模式实现线程池(懒汉方式)

六、其他常见的各种锁


一、什么是线程池

        线程池是线程的一种使用模式。在前面的情况中,我们都是遇到任务然后创建线程再执行。但是线程的频繁创建就类似于内存的频繁申请,会给操作系统带来更大的压力,进而影响整体的性能。

        所以我们一次申请好一定数量而定线程,然后将线程的管理操作交给线程池,就避免了在短时间内不断创建与销毁线程的代价,线程池不但能够保证内核的充分利用,还能防止过分调度,并根据实际业务情况进行修改。 

二、线程池的优点

  1. 任务来到立马就有线程去执行任务,节省了创建线程的时间。
  2. 防止服务器线程过多导致的系统过载问题
  3. 相对于进程池,线程池资源占用较少,但是健壮性很差 

三、线程池的应用

需要大量的线程来完成任务,且完成任务的时间比较短 

  • 例如:WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

对性能要求苛刻的应用

  • 比如要求服务器迅速响应客户请求。

接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用

  • 突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

四、实现一个简单的线程池

线程池中提供了一个任务队列,以及若干个线程。示意图如下:

thread_pool.hpp

#pragma once        
#include <iostream>    
#include <string>    
#include <queue>    
#include <unistd.h>    
#include <pthread.h> 
   
using namespace std;
    
namespace ns_threadpool    
{    
    const int g_num = 5;    
    template <class T>    
    class ThreadPool    
    {    
    private:    
        int num_; //固定大小的线程池   
        queue<T> task_queue_; //任务队列,使用STL的queue实现   
        pthread_mutex_t mtx_; //定义一把锁  
        pthread_cond_t cond_; //定义一个条件变量
   
    public:    
        void Lock() { pthread_mutex_lock(&mtx_);} //加锁操作     
    
        void Unlock() { pthread_mutex_unlock(&mtx_);} //解锁操作   
   
        bool IsEmpety() { return task_queue_.empty();} //判断任务队列是否为空  
    
        void Wait() { pthread_cond_wait(&cond_, &mtx_);} //让线程在条件变量下等待   
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
        void WakeUp() { pthread_cond_signal(&cond_);} //唤醒在条件变量下等待的线程   
   
    public:    
        ThreadPool(int num = g_num):num_(num)    
        {    
            pthread_mutex_init(&mtx_, nullptr);    
            pthread_cond_init(&cond_, nullptr);    
        }    
    
        //在类中要让线程执行类内成员方法,是不可行的    
        //必须让线程执行静态方法    
        static void* Rountine(void* args)    
        {    
            pthread_detach(pthread_self());    
            ThreadPool<T>* tp = (ThreadPool<T>*)args;    
            while(true)    
            {    
                tp->Lock();    
                while(tp->IsEmpety())    
                {    
                    tp->Wait();    
                }    
                T t;    
                tp->PopTask(&t);    
                tp->Unlock();    
                t.Run();    
    
            }    
        }    
    
        void InitThreadPool()    
        {    
            pthread_t tid;    
            for(int i = 0; i < num_; i++)    
            {    
                pthread_create(&tid, nullptr, Rountine, (void*)this);    
            }    
        }    
    
        void PushTask(const T& in)//向任务队列添加任务    
        {    
            Lock();    
            task_queue_.push(in);    
            Unlock();    
            WakeUp();    
        }    
    
        void PopTask(T* out)//从任务队列获取任务    
        {    
            *out = task_queue_.front();    
            task_queue_.pop();    
        }    
    
        ~ThreadPool()    
        {    
            pthread_mutex_destroy(&mtx_);    
            pthread_cond_destroy(&cond_);    
        }    
    };    
}  

Task.hpp

#pragma once                                                                                                                                                                                                                                                                                                                                                                
#include <iostream>    
#include <pthread.h>    
using namespace std;    
    
namespace ns_task    
{    
    class Task    
    {    
    private:    
        int x_;    
        int y_;    
        char op_;//用来表示:+ 、- 、* 、/ 、%    
    public:    
        Task(){}    
        Task(int x, int y, char op):x_(x), y_(y), op_(op){}    
    
        string show()    
        {    
            string message = to_string(x_);    
            message += op_;    
            message += to_string(y_);    
            message += "=?";    
            return message;    
        }    
        int Run()    
        {    
            int res = 0;    
            switch(op_)    
            {    
                case '+':    
                  res = x_ + y_;    
                  break;    
                case '-':    
                  res = x_ - y_;    
                  break;    
                case '*':    
                  res = x_ * y_;    
                  break;    
                case '/':    
                  res = x_ / y_;    
                  break;    
                case '%':    
                  res = x_ % y_;    
                  break;    
                default:    
                  cout << "bug" << endl;    
                  break;    
            }    
            printf("当前任务正在被:%lu处理,处理结果为:%d %c %d = %d\n",pthread_self(), x_, op_, y_, res);     
            return res;    
        }    
    
        int operator()()    
        {    
            return Run();    
        }    
    
        ~Task(){}    
    };    
} 

main.cc

#include "thread_pool.hpp"    
#include "Task.hpp"    
#include <ctime>    
#include <cstdlib> 
   
using namespace ns_threadpool;    
using namespace ns_task; 
   
int main()                                                                                                                                                                            
{    
    ThreadPool<Task>* tp = new ThreadPool<Task>();//创建线程池    
    tp->InitThreadPool();  //进行初始化  
    srand((long long)time(nullptr));//生产随机数    
    while(true) //不断向任务队列塞数据   
    {    
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);    
        tp->PushTask(t);    
        sleep(1);    
    }    
    return 0;    
}

相关说明:

        我们创建好了线程池之后,首次我们先是对其进行初始化操作;然后不断的向任务队列塞数据,由线程池中的线程去获取任务并执行相关操作;

        1.任务队列(即临界资源)是会被多个执行流同时访问,因此我们需要引入互斥锁对任务队列进行保护。

        2.线程池中的线程想要获取到任务队列中的任务,那么就必须要确保任务队列中有任务,所以我们还需引入条件变量来进行判断,如果队列中没有任务,线程池中的线程将会被挂起,直到任务队列中有任务后才被唤醒;

        3.在thread_pool.hpp中,多线程去执行对应的方法的时候,采用的是静态成员函数,这样做的目的是解决类中存在隐藏的this指针问题,因为多线程在调用对应的函数时,该函数只有一个形参,不加static的话,那么形参个数就有两个,是不可以的;所以我们可以将this指针作为参数传递过去,就可以访问类内的成员函数了;

运行代码后一瞬间就有六个线程,其中一个是主线程,另外五个是线程池内处理任务的线程。

五、单例模式

        单例(Singleton)模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例 ;

使用场景:

  1. 语义上只需要一个
  2. 该对象内部存在大量的空间,保存了大量的数据,如果允许该对象存在多份,或者允许发生各种拷贝,内存中存在冗余数据;

一般Singleton模式通常有三种形式:

  • 饿汉式:吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。
  • 懒汉式:吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。

        懒汉方式最核心的思想是 "延时加载"。(例如我们之前所学过的写时拷贝)从而能够优化服务器的启动速度。

1. 饿汉实现方式

该模式在类被加载时就会实例化一个对象,具体代码如下:

template <typename T> 
class Singleton 
{ 
private:
    static Singleton<T> data;//饿汉模式,在加载的时候对象就已经存在了 
public: 
    static Singleton<T>* GetInstance() 
    { 
        return &data; 
    } 
};

        该模式能简单快速的创建一个单例对象,而且是线程安全的(只在类加载时才会初始化,以后都不会)。但它有一个缺点,就是不管你要不要都会直接创建一个对象,会消耗一定的性能(当然很小很小,几乎可以忽略不计,所以这种模式在很多场合十分常用而且十分简单) 

2. 懒汉实现方式

该模式只在你需要对象时才会生成单例对象(比如调用GetInstance方法) 

template <typename T> 
class Singleton 
{ 
private:
    static Singleton<T>* inst; //懒汉式单例,只有在调用GetInstance时才会实例化一个单例对象
public: 
    static Singleton<T>* GetInstance() 
    { 
        if (inst == NULL) 
        { 
            inst = new Singleton<T>(); 
        }
    return inst; 
    } 
};

        看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有多个线程同时调用GetInstance()方法,由于当前还没有对象生成,那么就会由多个线程创建多个对象。

// 懒汉模式, 线程安全 
template <typename T> 
class Singleton 
{
private: 
    static Singleton<T>* inst; 
    static std::mutex lock; 
public: 
    static T* GetInstance() 
    { 
        if (inst == NULL) // 双重判定空指针, 降低锁冲突的概率, 提高性能 
        {                 
            lock.lock();  // 使用互斥锁, 保证多线程情况下也只调用一次 new
            if (inst == NULL) 
            { 
                inst = new T(); 
            }
            lock.unlock(); 
        }
    return inst;
    } 
};

        这种形式是在懒汉方式的基础上增加的,当多个线程调用GetInstance方法时,此时类中没有对象,那么多个线程就会来到锁的位置,竞争锁。必然只能有一个线程竞争锁成功,此时再次判断有没有对象被创建(就是inst指针),如果没有就会new一个对象,如果有就会解锁,并返回已有的对象;

        总的来说,这样的形式使得多个线程调用GetInstance方法时,无论成功与否,都会有返回值;

3. 单例模式实现线程池(懒汉方式)

thread_pool.hpp 

#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

using namespace std;

namespace ns_threadpool
{
    const int g_num = 5;
    template <class T>
    class ThreadPool
    {
    private:
        int num_;
        queue<T> task_queue_;
        pthread_mutex_t mtx_;
        pthread_cond_t cond_;
        static ThreadPool<T>* ins;//类内的静态指针
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
    private:
        //构造函数必须得实现,必须初始化
        ThreadPool(int num = g_num):num_(num)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&cond_, nullptr);
        }

        ThreadPool(const ThreadPool<T>& tp) = delete;

        ThreadPool<T>& operator=(ThreadPool<T>& tp) = delete;

    public:
        static ThreadPool<T>* GetInstance()
        {
            //使用静态的锁是不需要初始化和销毁的
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            if(ins == nullptr)//双判定,减少锁的征用,提高获取单例的效率
            {
                pthread_mutex_lock(&lock);
                if(ins == nullptr)
                {
                    ins = new ThreadPool<T>();
                    ins->InitThreadPool();
                    cout << "首次加载对象..." << endl;
                }
                pthread_mutex_unlock(&lock);
            }
            return ins;
        }
        void Lock() { pthread_mutex_lock(&mtx_);}

        void Unlock() { pthread_mutex_unlock(&mtx_);}

        bool IsEmpety(){ return task_queue_.empty();}

        void Wait() { pthread_cond_wait(&cond_, &mtx_);}

        void WakeUp() { pthread_cond_signal(&cond_);}
    public:

        //在类中要让线程执行类内成员方法,是不可行的
        //必须让线程执行静态方法
        static void* Rountine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T>* tp = (ThreadPool<T>*)args;
            while(true)
            {
                tp->Lock();
                while(tp->IsEmpety())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->Unlock();
                t.Run();

            }
        }

        void InitThreadPool()
        {
            pthread_t tid;
            for(int i = 0; i < num_; i++)
            {
                pthread_create(&tid, nullptr, Rountine, (void*)this);
            }
        }

        void PushTask(const T& in)
        {
            Lock();
            task_queue_.push(in);
            Unlock();
            WakeUp();
        }

        void PopTask(T* out)
        {
            *out = task_queue_.front();
            task_queue_.pop();
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&cond_);
        }
    };

    template<class T>
    ThreadPool<T>* ThreadPool<T>::ins = nullptr;

}

main.cc

#include "thread_pool.hpp"    
#include "Task.hpp"    
#include <ctime>    
#include <cstdlib>    
using namespace ns_threadpool;    
using namespace ns_task;    
int main()    
{    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    cout << "当前正在运行我的进程和其他代码......" << endl;    
    sleep(5);    
    srand((long long)time(nullptr));    
    while(true)    
    {    
        sleep(1);    
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);    
        ThreadPool<Task>::GetInstance()->PushTask(t);    
        //单列本身会在任何场景下,任何环境下被调用    
        //GetInstans():被多线程重入,进而导致线程安全的问题                                                                                                                          
        cout << ThreadPool<Task>::GetInstance() << endl;//获取对象的地址    
    }    
        return 0;    
} 

Task.hpp同上

运行后发现对象的地址是一样的,表明单例调用成功了,只存在一份;

六、其他常见的各种锁

悲观锁:
        在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
         悲观锁适用于写多读少的情况下,即:需要频繁的写数据时候,可以考虑使用悲观锁。
乐观锁:
        每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。
         乐观锁适用于读多写少的情况下,即:读数据多余写数据的时候,可以考虑使用乐观锁。
自旋锁:
         当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux —— 线程池 的相关文章

随机推荐

  • 83.Zabbix之iDrac的SNMP监控

    1 启动iDrac的SNMP服务 2 Zabbix server配置 使用系统自带的idrac模板 xff0c 然后全克隆成自己项目的模板 3 为主机添加模板 4 查看SNMP监控情况
  • MySQL查看SQL语句执行效率和mysql几种性能测试的工具

    Explain命令在解决数据库性能上是第一推荐使用命令 xff0c 大部分的性能问题可以通过此命令来简单的解决 xff0c Explain可以用来查看 SQL 语句的执行效 果 xff0c 可以帮助选择更好的索引和优化查询语句 xff0c
  • Huawei-FusionCompute-在Linux系统中安装Tools

    在 Linux 系统中安装 Tools 操作场景 空虚拟机创建并安装操作系统后 xff0c 需在虚拟机上安装华为提供的 Tools xff0c 以便提高虚拟机的 I O 处理性能 实现对虚拟机的硬件监控和其他高级功能 某些特性必须要安装 T
  • Huawei-FusionCompute-在Windows系统中安装Tools(x86)

    在 Windows 系统中安装 Tools xff08 x86 xff09 操作场景 空 x86 架构虚拟机创建并安装操作系统后 xff0c 需在虚拟机上安装华为提供的 Tools xff0c 以便提高虚拟机的 I O 处理性能 实现对虚拟
  • Huawei交换机链路聚合eth-trunk配置

    华三的动态对接华为的lacp 华三的静态对接华为的手工 华为手工模式配置 xff1a 1 执行命令 system view xff0c 进入系统视图 2 执行命令 interface eth trunk trunk id xff0c 进入
  • Windows每月安装补丁

    1 打开windows补丁的网站 Microsoft Update Catalog 2 搜索Cumulative Windows Server 2016 3 点击Download下载2023年4月补丁合集
  • Windows server 2022安装与激活

    1 下载 百度网盘下载 链接 xff1a https pan baidu com s 18c5smZPzbk0ClhEYh4LQ2w 提取码 xff1a w7i8 2 安装 最近版本新出的镜像官方的 EFI 文件在虚拟机上部署有问题 xff
  • 使用UltraISO制作U盘启动安装系统的方法

    1 下载并安装好UltraISO 下载链接ultraiso绿色版下载 ultraiso绿色版免费版下载v9 7 6 3810 软件爱好者 2 安装好UltraISO以后 xff0c 双击图标打开软件 3 点击 文件 菜单 打开 xff0c
  • u盘变成驱动器怎么变回来

    1 首先按下键盘 win 43 r 打开运行 xff0c 输入 cmd 回车确定 2 在其中输入 diskpart 回车确定 3 然后不要关闭 xff0c 右键此电脑 xff0c 打开 管理 4 在磁盘管理下确认一下自己的u盘盘符 5 确认
  • 全国DNS服务器IP地址大全详细介绍

  • Nginx之TCP/UDP反向代理

    Nginx从1 9 13起开始发布ngx stream core module模块不仅能支持TCP代理及负载均衡 其实也是支持UDP协议的 1 Nginx下载 wget http nginx org download nginx 1 24
  • 【无标题】

    错误代码 root 64 xxx nginx sbin nginx t nginx emerg unknown directive 34 stream 34 in usr local nginx conf nginx conf 118 ng
  • pm2常用的命令用法介绍

    pm2 是一个带有负载均衡功能的Node应用的进程管理器 当你要把你的独立代码利用全部的服务器上的所有CPU 并保证进程永远都活着 0秒的重载 PM2是完美的 下面我们来看pm2常用的命令用法介绍吧 PM2 xff08 github上的源码
  • 华为CE12808更换主控板

    在更换主控板的过程中 xff0c 务必缓慢 平稳地插拔单板 xff0c 避免左右晃动而导致触碰相邻单板 xff0c 造成正在运行中的单板发生故障 在主控板拔插的过程中严禁用手接触单板上的元器件 xff0c 以免损坏单板 新的主控板在出厂时已
  • 迪普防火墙FW1000普通双击热备配置

    一 组网图 二 端口聚合配置 三 配置三层接口地址 四 配置普通双机热备 五 配置安全域 六 配置地址对象 七 配置服务对象 八 配置静态路由 九 配置VRRP 十 配置NAT地址池 十一 配置源NAT 十二 配置目的NAT 十三 配置包过
  • 迪普防火墙F1000静默双击热备配置

    一 组网图 二 创建端口聚合 三 配置心跳接口IP地址 四 配置静默双击热备 五 配置三层接口IP地址 六 配置安全域 七 配置地址对象 八 配置服务对象 九 配置静态路由 十 配置NAT地址池 十一 配置源NAT 十二 配置目的NAT 十
  • Zabbix用户及用户组权限概述

    本文简单介绍用户及用户组权限 xff0c 参考官方文档 xff1a https www zabbix com documentation 5 2 manual config users and usergroups 一 概述 Zabbix
  • 使用nginx反向代理MRCP SERVER

    MRCP V2 的消息组成 MRCP xff08 V2 xff09 的交互过程可以分为三部分 1 SIP交互 xff1a Session Initiation Protocol xff0c 缩写SIP 正如协议的名称所言 xff0c 用于初
  • 一文搞懂如何使用STM32驱动直流电机(普通PWM输出和L298N、高级定时器输出带死区双通道互补PWM和IR2110S及自举电路、H桥电路和电机正反转)

    本文将用最通俗易懂的语言讲解怎么使用STM32驱动直流电机 xff0c 以及在使用过程中容易遇到的问题和解决办法 本文将介绍两种驱动方式 xff1a 普通PWM驱动L298N驱动直流电机 xff1b 互补PWM驱动IR2110S驱动直流电机
  • Linux —— 线程池

    目录 一 什么是线程池 二 线程池的优点 三 线程池的应用 四 实现一个简单的线程池 五 单例模式 1 饿汉实现方式 2 懒汉实现方式 3 单例模式实现线程池 xff08 懒汉方式 xff09 六 其他常见的各种锁 一 什么是线程池 线程池