Linux —— 线程池

article/2025/9/10 3:42: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();  // 使用互斥锁, 保证多线程情况下也只调用一次 newif (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同上

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

六、其他常见的各种锁

悲观锁:
        在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
         悲观锁适用于写多读少的情况下,即:需要频繁的写数据时候,可以考虑使用悲观锁。
乐观锁:
        每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。
         乐观锁适用于读多写少的情况下,即:读数据多余写数据的时候,可以考虑使用乐观锁。
自旋锁:
 当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。  

http://chatgpt.dhexx.cn/article/oj6Nx2tt.shtml

相关文章

线程池的实现原理

系统学习性&#xff0c;移步 IT-BLOG 线程池做的工作主要是控制运行的线程数量&#xff0c;处理过程中将任务放入队列&#xff0c;然后在线程创建后启动这些任务&#xff0c;如果线程数超过了最大数量超出数量的线程排队等候&#xff0c;等其他线程执行完毕&#xff0c;再从队列…

java——线程池

一、线程池 线程池可以看做是线程的集合。它的工作主要是控制运行的线程的数量&#xff0c;处理过程中将任务放入队列&#xff0c;然后在线程创建后 启动这些任务&#xff0c;如果线程数量超过了最大数量超出数量的线程排队等候&#xff0c;等其它线程执行完毕&#xff0c; 再…

java线程池(详解)

线程池介绍 线程池&#xff08;thread pool&#xff09;&#xff1a;一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;对线程统一管理。 线程池就是存放线程的池子&#xff0c;池子里存放了很多可以复…

Java线程池详解

本文包含知识点 线程池的使用场景分析线程池的创建及重要参数线程池实现线程复用的原理springboot中使用线程池Callabel与Runnable任务在基于spring体系的业务中正确地关闭线程池实现优先使用运行线程及调整线程数大小的线程池(线程池的优化)在java web项目中慎用Executors以及…

C++线程池

1.基础概念 线程池&#xff1a;一种线程的使用模式&#xff0c;线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性。而线程池维护着多个线程&#xff0c;等待监督管理者分配可并行执行的任务。这样避免了在短时间内创建和销毁线程的代价。线程池不仅能够内核的充分…

线程池详解

成功不是将来才有的&#xff0c;而是从决定去做的那一刻起&#xff0c;持续累积而成。 目录 背景 线程池介绍 线程池使用 Executors 线程池如何关闭&#xff1f; 面试题 总结 背景 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -&…

线程池(通俗易懂)

目录 一、什么是线程池 二、创建线程池的方式 三、线程池的七大参数 四、四种拒绝策略 1.AbortPolicy() 2.CallerRunsPolicy() 3.DiscardPolicy() 4.DiscardOldestPolicy() 五、自定义一个线程池 1.场景描述 2.代码实现 一、什么是线程池 线程池其实就是一种多线程处理…

线程池研发学习笔记

线程池研发 线程池 线程池基础 概念介绍 1:什么是线程池 可以直接叙述,也可以对比连接池介绍 线程池其实就是一种多线程处理形式&#xff0c;处理过程中可以将任务添加到队列中&#xff0c;然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是…

线程池是什么?线程池(ThreadPoolExecutor)使用详解

点一点&#xff0c;了解更多https://www.csdn.net/ 本篇文章将详细讲解什么是线程池&#xff0c;线程池的参数介绍&#xff0c;线程池的工作流程&#xff0c;使用Executors创建常见的线程池~~~ 目录 点一点&#xff0c;了解更多 文章目录 一、线程池的概念 1.1线程池的目的…

写给小白看的线程池,还有10道面试题

如何搞定20k的面试小抄 为什么要用线程池呢&#xff1f; 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -> {System.out.println("run thread->" Thread.currentThread().getName());userService.updateUser(....);}).start…

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础线程池使用线程池综合案例学员练习线程池总结 概念介绍 什么是线程池为什么使用线程池线程池有哪些优势 什么…

Java 多线程:彻底搞懂线程池

熟悉 Java 多线程编程的同学都知道&#xff0c;当我们线程创建过多时&#xff0c;容易引发内存溢出&#xff0c;因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列&#xff08;workQueue&#x…

GridView概述

一、使用GridView以表格形式显示多张图片 GridView用于在界面上按行、列分布的方式来显示多个组件 二、使用GridView 1、java代码 import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterV…

Master-Detail GridView

梦幻版Master-Detail GridView(黄忠成) 2007-12-26 09:34 前面的Master-Detail GridView控件應用&#xff0c;相信你已在市面上的書、或網路上見過&#xff0c;但此節中的GridView控件應用包你沒看過&#xff0c;但一定想過&#xff01;請見圖4-8-63。 圖4-8-63 圖 4-8-64 你一…

GridView DataGrid

ASP.NET 2.0提供了功能强大的数据绑定控件GridView、在使用中&#xff0c;一些属性和方法经常会与ASP.NET 1.1中的DataGrid混淆(VS2005中依然可以使用DataGrid&#xff0c;手动添加到工具箱或HTML状态输入代码)&#xff0c;下面我们分别用GridView和DataGrid实现其数据绑定、编…

GridView详讲

GridView是ASP.NET界面开发中的一个重要的控件&#xff0c;对GridView使用的熟练程度直接影响软件开发的进度及功能的实现。(车延禄) GridView的主要新特性&#xff1a; 1.与DataSource控件结合实现了显示与数据操作的分离&#xff0c;大大减化了代码的编写量; 2.实现"双向…

GridView详述

GridView无代码分页排序GridView选中&#xff0c;编辑&#xff0c;取消&#xff0c;删除GridView正反双向排序GridView和下拉菜单DropDownList结合GridView和CheckBox结合鼠标移到GridView某一行时改变该行的背景色方法一鼠标移到GridView某一行时改变该行的背景色方法二GridVi…

GridView、ListView、Adapter、Map、HashMap

1.ListView自定义适配器adapter 注&#xff1a;Android适配器是数据和视图之间的桥梁&#xff0c;以便于数据在View上显示。适配器就像显示器&#xff0c;把复杂的东西按人可以接受的方式来展现。 &#xff08;1&#xff09;首先将适配器的View视图表现出来&#xff0c;使用L…

GridViewPager

GridViewPager ViewPager结合GridView&#xff0c;轻松实现类似表情面板的控件。可自由定制Item布局&#xff0c;提供充足的自定义参数等。也处理了条目点击事件和条目长按事件。效果如下&#xff1a; Demo下载地址&#xff1a;GridViewPager &#xff0c;或者扫描以下二维码…

libevent 编译

1.下载源码 github:https://github.com/libevent/libevent 官网&#xff1a;http://libevent.org/ 2.CMake 编译 在libevent源码目录建立文件夹&#xff1a;BuildVs2010_x64 2.打开CMake 3.BuildVs2010_x64 下此时生成了vs2010的解决方案。然后编译生成就ok NOTE&#x…