【Linux】线程池

2023-11-02

1.线程池概念

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

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线
程,等待着监督管理者分配可并发执行的任务。

2.线程池的优点

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

可用线程的数量取决于可用的并发处理器、处理器内核、内存、网络、sockets等。

3.线程池的应用场景

(1).需要大量的线程来完成任务,且完成任务的时间比较短。

比如:WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大 。

(2). 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

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

突发性大量客户请求,在没有线程池情况下,将产生大量线程,短时间内产生大量线程可能使内存到达极限。

4.线程池的实现

线程池对外暴露一个接口push接口,用于任务的加入。

图示:

image-20221123154359756

threadpool.hpp

实现线程模板:

#include <iostream>
#include <assert.h>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
using namespace std;


const int Thread_num=10;
template <class T>
class threadpool{
private:
    threadpool(const int num=Thread_num):threadnum(num)
    {
        assert(threadnum>0);
        isrunning=false;
        pthread_cond_init(&cond_,nullptr);
        pthread_mutex_init(&mutex_,nullptr);
    }
    threadpool(const threadpool<T> &)=delete;    //拷贝构造
    threadpool<T>& operator=(const threadpool<T>&)=delete;
public:
    ~threadpool(){
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    static threadpool<T>*getInstance(){
        static pthread_mutex_t mutex_static;
        if(instance==nullptr){
            //判断是否需要创建线程池
            pthread_mutex_lock(&mutex_static);
            if(instance==nullptr){
                instance=new threadpool<T>();
            }
            pthread_mutex_unlock(&mutex_static);
        }
        pthread_mutex_destroy(&mutex_static);
        return instance;
    }
    //类内函数默认第一个参数是this指针,而由于线程的执行函数只有一个参数,所以设置为静态函数,否则第一个参数被this占用
    static void* threadroutine(void* arg){
        pthread_detach(pthread_self()); //分离线程
        threadpool<T>* pool=static_cast<threadpool<T>* >(arg);
        prctl(PR_SET_NAME, "follower");
        //线程不断的获取任务,并执行
        while (true)
        {
            pool->lockQueue();
            //判断是否为空
            //为空则等待唤醒
            //不为空,取任务执行
            while (!pool->haveTask())
            {
                pool->waitForTask();
            }
            T t=pool->pop();
            int em1,em2;
            char op;
            t.get(&em1,&em2,&op);
            cout << "consumer[" << pthread_self() << "] " << (unsigned long)time(nullptr) 
            << " 消费了一个任务: " << em1 << op << em2 << "=" << t() << endl;
            pool->unlockQueue();
        }
        return nullptr;
    }
    void start(){
        assert(!isrunning);
        for(int i=0;i<threadnum;i++){
            pthread_t tid;
            pthread_create(&tid,nullptr,threadroutine,(void*)instance);
        }
    }

    void push(const T& t)
    {
        lockQueue();
        workqueue_.push(t);
        choiceThreadForHandler();
        unlockQueue();
    }
private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !workqueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp =workqueue_.front();
        workqueue_.pop();
        return temp;
    }
    queue<T> workqueue_;    //工作队列
    int threadnum;  //线程数量
    pthread_mutex_t mutex_; 
    pthread_cond_t cond_;
    bool isrunning; //判断线程池是否允许
    static threadpool<T>* instance;
};

template<class T>
threadpool<T>* threadpool<T>::instance=nullptr;

注意点:

  • 当某线程被唤醒时,其可能是被异常或是伪唤醒,或者是一些广播类的唤醒线程操作而导致所有线程被唤醒,使得在被唤醒的若干线程中,只有个别线程能拿到任务。此时应该让被唤醒的线程再次判断是否满足被唤醒条件,所以在判断任务队列是否为空时,应该使用while进行判断,而不是if。
  • pthread_cond_broadcast函数的作用是唤醒条件变量下的所有线程,而外部可能只Push了一个任务,我们却把全部在等待的线程都唤醒了,此时这些线程就都会去任务队列获取任务,但最终只有一个线程能得到任务。一瞬间唤醒大量的线程可能会导致系统震荡。这个现象也叫做惊群效应

为什么线程函数要设置为静态类型?

  • 类内函数默认第一个参数是this指针,而由于线程的执行函数只有一个参数,所以设置为静态函数,否则第一个参数被this占用
  • 静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的this指针的,因此我们需要将Routine设置为静态方法,此时Routine函数才真正只有一个参数类型为void*的参数。

测试文件

#include "threadpool.hpp"
#include "task.hpp"
#include <ctime>
#include <thread>

const std::string ops = "+-*/%";
int main()
{

    prctl(PR_SET_NAME, "main");
    //使用智能指针
    unique_ptr<threadpool<Task>>pool(threadpool<Task>::getInstance());
    pool->start();
    //生产任务
    srand((unsigned long)time(nullptr) ^ getpid() ^ pthread_self());
    while (true)
    {
        int em1=rand()%100,em2=rand()%30;
        char op=ops[rand()%4];
        Task t(em1,em2,op);
        pool->push(t);
        cout << "producter[" << pthread_self() << "] " << (unsigned long)time(nullptr) 
        << " 生产了一个任务: " << em1 << op << em2 << "=?" << endl;
        sleep(1);
    }
    return 0;
}

使用下面的指令对轻量级线程进行监控:

while :; ps -aL|grep -1&&ps -aL|grep threadpool_test|grep -v grep;echo "#######";sleep 1;done

执行结果:

image-20221123172208398

5.STL和智能指针和线程安全

STL中的容器不是线程安全的

  • STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响。
  • 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
  • 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全

智能指针是否是线程安全的?

  • 对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
  • 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数

5.1其他常见锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,
    行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试

自旋锁和挂起等待锁

  • 自旋锁:轮询检测锁是否锁就绪

挂起等待锁适合在临界区长时间允许占有锁的情况。而挂起等待锁适合在临界区运行时间短,等待锁时间短的情况。

自旋锁的接口

pthread_spin_init();
与互斥锁的接口一样,只需要将mutex修改为spin即可

5.2读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读
的机会反而高的多。

通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。而读写锁就可以解决读多写少的情况。

image-20221123173603762

  • **注意:写独占,读共享,读锁优先级高 **

接口

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

**加锁和解锁 **

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】线程池 的相关文章

  • 如何将 std::string& 转换为 C# 引用字符串

    我正在尝试将 C 函数转换为std string参考C 我的 API 如下所示 void GetStringDemo std string str 理想情况下 我希望在 C 中看到类似的东西 void GetStringDemoWrap r
  • 通过引用传递 [C++]、[Qt]

    我写了这样的东西 class Storage public Storage QString key const int value const void add item QString int private QMap
  • 如何在 Cassandra 中存储无符号整数?

    我通过 Datastax 驱动程序在 Cassandra 中存储一些数据 并且需要存储无符号 16 位和 32 位整数 对于无符号 16 位整数 我可以轻松地将它们存储为有符号 32 位整数 并根据需要进行转换 然而 对于无符号 64 位整
  • 机器Epsilon精度差异

    我正在尝试计算 C 中双精度数和浮点数的机器 epsilon 值 作为学校作业的一部分 我在 Windows 7 64 位中使用 Cygwin 代码如下 include
  • C++11 删除重写方法

    Preface 这是一个关于最佳实践的问题 涉及 C 11 中引入的删除运算符的新含义 当应用于覆盖继承父类的虚拟方法的子类时 背景 根据标准 引用的第一个用例是明确禁止调用某些类型的函数 否则转换将是隐式的 例如最新版本第 8 4 3 节
  • 传递给函数时多维数组的指针类型是什么? [复制]

    这个问题在这里已经有答案了 我在大学课堂上学习了 C 语言和指针 除了多维数组和指针之间的相似性之外 我认为我已经很好地掌握了这个概念 我认为由于所有数组 甚至多维 都存储在连续内存中 因此您可以安全地将其转换为int 假设给定的数组是in
  • 从经典 ASP 调用 .Net C# DLL 方法

    我正在开发一个经典的 asp 项目 该项目需要将字符串发送到 DLL DLL 会将其序列化并发送到 Zebra 热敏打印机 我已经构建了我的 DLL 并使用它注册了regasm其次是 代码库这使得 IIS 能够识别它 虽然我可以设置我的对象
  • 如何连接重叠的圆圈?

    我想在视觉上连接两个重叠的圆圈 以便 becomes 我已经有部分圆的方法 但现在我需要知道每个圆的重叠角度有多大 但我不知道该怎么做 有人有主意吗 Phi ArcTan Sqrt 4 R 2 d 2 d HTH Edit 对于两个不同的半
  • 无限循环与无限递归。两者都是未定义的吗?

    无副作用的无限循环是未定义的行为 看here https coliru stacked crooked com view id 24e0a58778f67cd4举个例子参考参数 https en cppreference com w cpp
  • 用于 FTP 的文件系统观察器

    我怎样才能实现FileSystemWatcherFTP 位置 在 C 中 这个想法是 每当 FTP 位置添加任何内容时 我都希望将其复制到我的本地计算机 任何想法都会有所帮助 这是我之前问题的后续使用 NET 进行选择性 FTP 下载 ht
  • 访问外部窗口句柄

    我当前正在处理的程序有问题 这是由于 vista Windows 7 中增强的安全性引起的 特别是 UIPI 它阻止完整性级别较低的窗口与较高完整性级别的窗口 对话 就我而言 我想告诉具有高完整性级别的窗口进入我们的应用程序 它在 XP 或
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • ASP.NET Core 3.1登录后如何获取用户信息

    我试图在登录 ASP NET Core 3 1 后获取用户信息 如姓名 电子邮件 id 等信息 这是我在登录操作中的代码 var claims new List
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • 为什么 C# 2.0 之后没有 ISO 或 ECMA 标准化?

    我已经开始学习 C 并正在寻找标准规范 但发现大于 2 0 的 C 版本并未由 ISO 或 ECMA 标准化 或者是我从 Wikipedia 收集到的 这有什么原因吗 因为编写 审查 验证 发布 处理反馈 修订 重新发布等复杂的规范文档需要
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • C# 中的 IPC 机制 - 用法和最佳实践

    不久前我在 Win32 代码中使用了 IPC 临界区 事件和信号量 NET环境下场景如何 是否有任何教程解释所有可用选项以及何时使用以及为什么 微软最近在IPC方面的东西是Windows 通信基础 http en wikipedia org
  • 对于某些 PDF 文件,LoadIFilter() 返回 -2147467259

    我正在尝试使用 Adob e IFilter 搜索 PDF 文件 我的代码是用 C 编写的 我使用 p invoke 来获取 IFilter 的实例 DllImport query dll SetLastError true CharSet
  • 指针和内存范围

    我已经用 C 语言编程有一段时间了 但对 C 语言还是很陌生 有时我对 C 处理内存的方式感到困惑 考虑以下有效的 C 代码片段 const char string void where is this pointer variable l
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么

随机推荐

  • VM关闭防火墙操作

    VM关闭防火墙操作 链接外网配置 setup 配置ip等 设置开启网卡 进入ifcfg eth0 vim etc sysconfig network scripts ifcfg eth0 修改 ONBOOT yes 重启网卡 service
  • 一、C语言进阶:文件操作

    1 文件操作 1 1 文件的输入输出 输出 使用printf 和命令行重定向 gt 实现文件输出 输入 使用scanf 和命令行重定向 lt 实现文件输入 include
  • Sass/Scss样式复用

    在团队开发中 复用代码能减少冗余 抹平编写风格的差异 或者在编写同主题的组件 统一文字 颜色 间隔等样式 复用样式代码是必不可少的 Sass提供四种样式复用的方法 import extend media mixin import Sass
  • Flutter 切换tabbar 视图重新渲染解决方法

    gt 底部tabbar点击page切换时 会重新加载页面 重新请求接口浪费资源 为解决这个尴尬处境 有个简易的办法在bottomtabbar类中 对 return scaffold body 加入indexedstack body Inde
  • e4a锁屏接收服务器信息,Arduino+ESP8266接收服务器信息

    上一篇文章 Arduino ESP8266上传数据到服务器 我们介绍了Arduino如何将数据上传到服务器上 与之相对应的是如何终端读取服务器的数据 在这一篇文章中我们将进行详细的讲解 为了便于说明 我们先演示一下如何 手动 的上传 读取数
  • Linux系统管理:虚拟机ESXi安装

    目录 一 理论 1 VMware Workstation 2 VMware vSphere Client 3 ESXi 二 实验 1 ESXi 7安装 一 理论 1 VMware Workstation 它是一款专业的虚拟机软件 可以在一台
  • Qt 命令行编译,VC命令行编译,调用nsis

    QT bat脚本 rd q s dist mkdir dist qmake exe helloworld pro spec win32 g CONFIG qtquickcompiler o dist cd dist mingw32 make
  • make[1]: *** Waiting for unfinished jobs....

    安装node js环境时候 make报错 报错信息 make 1 Waiting for unfinished jobs rm 490f1fcf42b2afac71d1c00fb593c736d4a65552 intermediate ma
  • Python执行JS代码的三种方式

    1 使用 js2py 基本操作 import js2py 执行单行js语句 js2py eval js console log abcd gt gt gt abcd 执行js函数 add js2py eval js function add
  • 基于session和token的身份认证方案

    一 基于session的身份认证方案 1 方案图示 2 比较通用的鉴权流程实现如下 在整个流程中有两个拦截器 第一个拦截器 AuthInteceptor是为了每一次的请求的时候都先去session中取user对象 如果session中有 就
  • 组合排列——回溯法的实践

    一 模板 对于回溯问题 可以给一个模板 result def backtracking 参数 if 终止条件 result add 路径 return for 选择 本层集合中元素 树中节点孩子的数量就是集合的大小 处理节点 backtra
  • 微信小程序 组件间关系

    完整微信小程序 Java后端 技术贴目录清单页面 必看 定义和使用组件间关系 有时需要实现这样的组件
  • Linux 压缩、解压文件的 4 种方式。tar、gzip、gunzip、zip、unzip、7z命令使用方法

    Linux 压缩 解压文件的 4 种方式 tar gzip gunzip zip unzip 7z命令使用方法 文章目录 Linux 压缩 解压文件的 4 种方式 tar gzip gunzip zip unzip 7z命令使用方法 1 t
  • JS 读写文件

    用js不能直接读取文件 但是可以利用浏览器提供的activex来实现读写文件的方法 只在IE下测试过 其他浏览器下的activex对象不太清楚 可以网上搜一下 具体读写文件的代码如下 function createAndReadFile v
  • UE4 - 海洋材质水下效果的修改

    屏幕前的污渍MASK修改位置如下 水下扭曲效果 强度修改位置如下 这里改0 1是无效的 只有0和1的区别 如果要调整波纹强度 需要到材质里修改 500的强度改为200 或者100 就很弱了 镜头光晕增加的地方
  • ThreadLocal与InheritableThreadLocal的实现原理

    文章目录 ThreadLocal介绍 使用方式 set 问题 InheritableThreadLocal介绍 源码 方案 ThreadLocal介绍 threadLocal的特点就是与线程绑定 一般通过这种隐式传参的方式来传递上下文 比如
  • vue实现高德地图点聚合功能

    效果截图展示 高德地图点聚合功能 1 创建地图 new AMap Map 示例 this map new AMap Map container resizeEnable true 是否监控地图容器尺寸变化 center 105 34 初始化
  • 终于来了!耗时268天,7大模块、2983页58万字,Android开发核心知识笔记!对标阿里P7!

    版权声明 本文为博主原创文章 未经博主允许不得转载 https www jianshu com u 3348b92f77a4 前言 转眼就快到 金九银十 又是个面试求职的黄金期 近来许多网友都在求一份完整 系统的学习资料和最新的大厂面试真题
  • Objective-C中的@dynamic

    Objective C中的 dynamic 一 dynamic与 synthesize的区别 property有两个对应的词 一个是 synthesize 一个是 dynamic 如果 synthesize和 dynamic都没写 那么默认
  • 【Linux】线程池

    文章目录 1 线程池概念 2 线程池的优点 3 线程池的应用场景 4 线程池的实现 5 STL和智能指针和线程安全 5 1其他常见锁 5 2读写锁 1 线程池概念 线程池是一种线程使用模式 线程过多会带来调度开销 进而影响缓存局部性和整体性