Linux——线程池

目录

线程池的概念

线程池的优点

线程池的实现

【注意】

线程池的线程安全

日志文件的实现


线程池的概念

        线程池也是一种池化技术,可以预先申请一批线程,当我们后续有任务的时候就可以直接用,这本质上是一种空间换时间的策略。

        如果有任务来的时候再创建线程,那成本又要提高,又要初始化,又要创建数据结构。

线程池的优点

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

线程池的实现

        我们这次要实现的线程池就是这样,让主线程派发任务,让线程池中的线程处理任务,这也是一个生产者消费者模型。

// thread.hpp
// 把线程封装一下
#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <vector>
#include <queue>
#include <unistd.h>

using namespace std;

typedef void*(*func_t)(void*);

class ThreadData
{
public:
    string name_;
    void* args_;
};

class Thread
{
public:
    Thread(int num, func_t callback, void* args)
        :func_(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);
        name_ = nameBuffer;

        tdata_.args_ = args;
        tdata_.name_ = name_;
    }
    void start()
    {
        pthread_create(&tid_, nullptr, func_, (void*)&tdata_);
    }
    void join()
    {
        pthread_join(tid_, nullptr);
    }
    string name()
    {
        return name_;
    }
    ~Thread()
    {}
private:
    string name_;
    pthread_t tid_;
    ThreadData tdata_;
    func_t func_;
};
// threadPool.hpp

#pragma once

#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"

const int g_default_num = 3;

template <class T>
class ThreadPool
{
public:
    // 通过接口获得成员变量
    pthread_mutex_t* getMutex()
    {
        return &lock_;
    }
    void waitCond()
    {
        pthread_cond_wait(&cond_, &lock_);
    }
    bool isEmpty()
    {
        return task_queue_.empty();
    }
public:
    ThreadPool(int thread_num = g_default_num) // 初始化后,就已经有了对象,也有了this指针
        :num_(thread_num)
    {
        pthread_mutex_init(&lock_, nullptr);
        pthread_cond_init(&cond_, nullptr);
        for (int i = 0; i < num_; i++)
        {
            threads_.push_back(new Thread(i + 1, routine, this) ); // 通过传入this指针就可以拿到ThreadPool中的task_queue
        }
    }
    void run()
    {
        for (auto& iter : threads_)
        {
            iter->start();
            cout << iter->name() << "启动成功" << endl;
        }
    }

    // 去掉this指针
    // 消费的过程
    static void* routine(void* args)
    {
        ThreadData* td = (ThreadData*)args;
        ThreadPool<T>* tq = (ThreadPool<T>*)td->args_; // 去掉this指针就无法访问成员方法了,通过创建线程的时候传入this拿到线程池对象
        while (true)
        {
            T task;
            {
                lockGuard lockguard(tq->getMutex());  // 加锁
                while (tq->isEmpty()) tq->waitCond(); // 检测

                // 读取任务
                task = tq->getTask();
            }
            // 仿函数
            cout << td->name_ << ", 消费者:" << task._x << " + " << task._y << " = " << task() << endl;
            
            // sleep(1);
        }
    }
    void pushTask(const T& task)
    {
        lockGuard lockguard(&lock_);
        task_queue_.push(task);
        pthread_cond_signal(&cond_);
    }
    T getTask()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
    void joins()
    {
        for (auto& iter : threads_)
        {
            iter->join();
        }
    }
    ~ThreadPool()
    {
        for (auto& iter : threads_)
        {
            delete iter;
        }
        pthread_mutex_destroy(&lock_);
        pthread_cond_destroy(&cond_);
    }
private:
    vector<Thread*> threads_;
    int num_;
    queue<T> task_queue_;  // 任务队列
    pthread_mutex_t lock_; // 互斥锁
    pthread_cond_t cond_;  // 条件变量
};
// testMain.cc
#include "threadPool.hpp"
#include "Task.hpp"
#include <ctime>

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    srand((unsigned)time(nullptr));
    cout << "hello thread pool" << endl;
    ThreadPool<Task> *tp = new ThreadPool<Task>();
    tp->run();

    while (true)
    {
        int x = rand() % 10 + 1;
        usleep(rand() % 1000);
        int y = rand() % 10 + 1;
        Task t(x, y, Add);
        tp->pushTask(t);
        cout << "生产者:" << x << " + " << y << " = ? " << endl;
        //sleep(1);
    }

    tp->joins();
    return 0;
}

【注意】

  1. 线程池中的任务队列会被多个执行流访问,因此我们需要互斥锁对任务队列进行保护。
  2. 线程池中的线程要从任务队列中拿任务,所以任务队列中必须要先有任务,必须要加锁循环检测,如果任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒,这些操作都是通过加锁和条件变量完成的
  3. 主线程向任务队列中push一个任务后,此时可能有线程正处于等待状态,所以在新增任务后需要唤醒在条件变量下等待的线程
  4. 某线程从任务队列中拿到任务后,该任务就已经属于当前线程了,所以解锁之后再进行处理任务,让加锁的动作更细粒度,也因为处理任务的过程会耗费时间,所以不要将处理动作其放到临界区当中
  5. 要给执行线程函数用static修饰,这个函数的类型必须是void* (*callback)(void*);如果放到类中,该函数就会多一个this指针。但是让他变成静态函数又不能访问线程池中的任务队列,所以要在线程创建的时候把线程池的对象指针传过去,因为初始化列表后已经有了对象,所以一定有this指针。也因为这个函数没有this指针,所以一些类内的操作要提供接口

线程池的线程安全

        我们也可以把线程池变成单例模式(懒汉模式)的,让整个进程只有一个线程池,但是如果以后有多个线程同时访问,同时判断这个单例对象存不存在,那就会有线程安全的问题。

class ThreadPool
{
// ...
private: // 私有构造,删除拷贝构造和赋值重载
    ThreadPool(int thread_num = g_default_num) // 初始化后,就已经有了对象,也有了this指针
        :num_(thread_num)
    {
        pthread_mutex_init(&lock_, nullptr);
        pthread_cond_init(&cond_, nullptr);
        for (int i = 0; i < num_; i++)
        {
            threads_.push_back(new Thread(i + 1, routine, this) ); // 通过传入this指针就可以拿到ThreadPool中的task_queue
        }
    }
    ThreadPool(const ThreadPool<T>&) = delete;
    const ThreadPool<T> operator=(const ThreadPool<T>& ) = delete;

public:
    static ThreadPool<T>* getThreadPool(int num = g_default_num) // 通过getThreadPool获取线程池
    {
        // 只有第一次为空的时候才创建,如果不为空直接返回thread_ptr,这样指针就只有一个
        {
            lockGuard lockguard(&mutex);
            if (nullptr == thread_ptr)
            {
                thread_ptr = new ThreadPool<T>(num);
            }
        }
        return thread_ptr;
    }
// ...

private:
    // 添加静态成员变量
    static ThreadPool<T>* thread_ptr; // 单例模式
    static pthread_mutex_t mutex;
};
// 初始化
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr = nullptr;

template<class T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER; 

        这样就可以保证,第一次获取ThreadPool对象的时候,多个线程访问就是安全的。但这就带来了另一个问题,如果每次想要获取ThreadPool对象的时候就会申请释放锁,这个行为也是在浪费资源,所以还要再调整一下。

static ThreadPool<T>* getThreadPool(int num = g_default_num)
{
    // 只有第一次为空的时候才创建,如果不为空直接返回thread_ptr,这样就只new了一次
    if (nullptr == thread_ptr) // 多判断一次不就可以了吗,已经创建了就直接返回,没有就加锁创建
    {
        lockGuard lockguard(&mutex);
        if (nullptr == thread_ptr)
        {
            thread_ptr = new ThreadPool<T>(num);
        }
    }
    return thread_ptr;
}

这样使用双重判定空指针就减少了大量已经创建好单例,其他线程还在请求锁的行为。

日志文件的实现

我们需要用到下面这些接口。

// log.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstdarg>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <ctime>

// 日志级别
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char* gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

// 完整的日志功能,至少有:日志等级 时间 日志内容 支持用户自定义
void logMessage(int level, const char* format, ...) // 最后一个参数就是可变参数列表
{
    char stdBuffer[1024]; // 日志的标准部分
    time_t timestamp = time(nullptr); // 时间戳
    snprintf(stdBuffer, sizeof(stdBuffer), "[%s][%ld]", gLevelMap[level], timestamp);

    char logBuffer[1024]; // 自定义部分
    va_list args; // 可变参数列表
    va_start(args, format);
    vsnprintf(logBuffer, sizeof (logBuffer), format, args); // 用起来和printf相差不多
    va_end(args);

    // printf("%s%s\n", stdBuffer, logBuffer); // 打印到显示器
    FILE* fp = fopen("log.txt", "a");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer); // 打印到文件
    fclose(fp);
}

        所以以后如果要用到这些线程池、日志文件等,就直接用了。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm+vue的校园驿站管理系统+(源码+部署说明+演示视频+源码介绍)

第1章绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。所以各行业&#xff0c;尤其是规模较大的企业和学校等…

TypeScript(五)交叉类型,联合类型,映射类型

交叉类型 交叉类型是将多个类型合并为一个类型。可以把现有的多种类型叠加到一起成为一种类型&#xff0c;它包含了所需的所有类型的特性。使用符号 & 表示。交叉类型 A & B 表示&#xff0c;任何一个新类型必须同时属于 A 和 B&#xff0c;才属于交叉类型 A & B …

Discuz! X3.5精品模板下载网站模板utf-8

适合做模板下载网站&#xff0c;模板涵盖广告设计/电商设计/海报/名片/字体/展板/X展架,下载即用,精品优质,海量免费模板网下载下载,专业模板素材网站,让设计变得更简单! 下载地址&#xff1a;Discuz! X3.5精品模板下载网站模板.zip 截图&#xff1a;

基于ESTAR指数平滑转换自回归模型的CPI数据统计分析matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 ESTAR模型概述 4.2 WNL值&#xff0c;P值&#xff0c; Q值&#xff0c;12阶ARCH值 4.3ADF检验 5.完整程序 1.程序功能描述 基于ESTAR指数平滑转换自回归模型的CPI数据统计分析matlab仿…

拼多多根据关键词取商品列表 API 返回值说明

一、应用场景 拼多多根据关键词取商品列表的API应用场景非常广泛&#xff0c;主要集中在电商领域&#xff0c;包括但不限于以下几个方面&#xff1a; 1、商品搜索与推荐&#xff1a;商家可以通过API接口&#xff0c;根据用户输入的关键词&#xff0c;实时获取拼多多平台上的相…

力扣hot100:34. 在排序数组中查找元素的第一个和最后一个位置(二分查找的理解)

我们知道使用二分查找能找到值所在的位置。假如我们在找到值后仍然不断的更新指针会发生什么&#xff1f;我们可以利用这一点来找到最左边的以及最右边的值。 如果当nums[mid]target时&#xff0c;使得 rightmid-1&#xff0c;那么最终会使得target在right的右边。 如果当nums[…

MacBook使用——彻底卸载并删除软件:NTFS for Mac

问题 之前因MacBook读写NTFS格式移动硬盘&#xff0c;我安装并使用了 Paragon NTFS for Mac &#xff0c;试用期结束后将其从【应用程序】中卸载移除了。但之后每次开机启动时&#xff0c;系统还是会弹出【激活】通知&#xff0c;如下图 解决 Step1、在用户目录下的 Library 目…

C++笔记:从零开始一步步手撕红黑树

文章目录 红黑树概念红黑树的性质红黑树 VS AVL树红黑树的结点与树的描述——定义类红黑树的插入操作步骤一&#xff1a;按照二叉搜索树的规则插入新结点步骤二&#xff1a;检测新节点插入后&#xff0c;红黑树的性质是否造到破坏情况一&#xff1a;uncle存在且为红情况二&…

外包干了9天,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2018年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

【微服务】分布式调度框架PowerJob使用详解

目录 一、前言 二、定时任务调度框架概述 2.1 为什么需要定时任务调度框架 2.2 定时任务调度使用场景 三、PowerJob 介绍 3.1 PowerJob 概述 3.2 PowerJob 功能特性 3.3 PowerJob 应用场景 3.4 PowerJob 与其他同类产品对比 四、PowerJob 部署 4.1 PowerJob 架构 4.…

【linux】进程(一)

先看预备知识&#xff0c;对本篇文章更有帮助。 目录 进程概念&#xff1a;了解动态运行的概念&#xff1a;进程的本身内部属性&#xff1a;启动进程&#xff1a;关闭进程&#xff1a; 如何创建进程&#xff1a;进程状态&#xff1a;直接看进程状态&#xff1a;僵尸进程与孤儿…

工智能的迷惑是技术发展的产物

简述&#xff1a; 随着ChatGPT在全球科技舞台上掀起一股热潮&#xff0c;人工智能再次成为了人们关注的焦点。各大公司纷纷紧跟潮流&#xff0c;推出了自己的AI大模型&#xff0c;如&#xff1a;文心一言、通义千问、讯飞星火、混元助手等等&#xff0c;意图在人工智能领域占据…

HarmonyOS NEXT应用开发—状态栏显隐变化

介绍 本示例介绍使用Scroll组件的滚动事件 onScroll 实现状态栏显隐变化。该场景多用于各种软件的首页、我的等页面中。 效果预览图 使用说明 加载完成后显示状态栏显隐变化页面&#xff0c;上下拖动屏幕&#xff0c;顶端状态栏出现显隐变化。 实现思路 在置顶位置使用sta…

三维坐标系之间的转换

一、概括 这个完全是抄别人的&#xff0c;给我自己看的&#xff0c;后期会吧代码更新上去。 彻底搞懂“旋转矩阵/欧拉角/四元数”&#xff0c;让你体会三维旋转之美_欧拉角判断动作-CSDN博客 在不同的坐标系中物体的位姿是相对的&#xff0c;任何的坐标系都是相对来说的&…

sql查询语句中提取【】里面的值

在实际工作中&#xff0c;有时候会遇到提取sql查询出来的字段中括号里面的码值&#xff0c;比如&#xff1a; 我现在要提取student表的sname中括号里面的字段 解决方法如下&#xff1a; select sname,replace(substr(sname, instr(sname, [,1)1),],) from student;成功提取&am…

BIG-Bench Hard 数据集分享

来源: AINLPer公众号&#xff08;每日干货分享&#xff01;&#xff01;&#xff09; 编辑: ShuYini 校稿: ShuYini 时间: 2024-3-17 该数据集由Google、斯坦福等研究人员开发&#xff0c;BBH的全称是BIG-Bench Hard&#xff0c;它是BIG-Bench数据集的一个子集&#xff0c;它专…

说JS作用域,就不得不说说自执行函数

一个兜兜转转&#xff0c;从“北深”回到三线城市的小码农&#xff0c;热爱生活&#xff0c;热爱技术&#xff0c;在这里和大家分享一个技术人员的点点滴滴。欢迎大家关注我的微信公众号&#xff1a;果冻想 前言 不得不吐槽&#xff0c;学个JS&#xff0c;这个概念也太多了&am…

Java Web项目—餐饮管理系统Day06-套餐管理(一)

文章目录 1. 需求分析与实体类准备2. 依据菜品分类或者名字进行查询的请求(需求B)3. 新增套餐 1. 需求分析与实体类准备 如上图为新增套餐的界面, 它包含了套餐的一些基本信息, 例如名称、价格等, 同时还有套餐分类(因此这里需要一个查询所有套餐分类的请求处理方法, 需求A). 以…

Twincat实现电机控制

不仅是控制系统的核心部分&#xff0c;而且能够将任何基于PC的系统转换为一个带有PLC、NC、CNC和机器人实时操作系统的实时控制系统。TwinCAT软件在工业自动化领域具有广泛的应用&#xff0c;特别是在机器人关节电机控制方面!!! 在机器人关节电机控制方面&#xff0c;TwinCAT通…

Go——运算符,变量和常量,基本类型

一.运算符 Go语言内置的运算符有&#xff1a; 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 1.1 算术运算符 注意&#xff1a;(自增)和--(自减)在go语言中是单独的语句&#xff0c;并不是运算符。 1.2 关系运算符 1.3 逻辑运算符 1.4 位运算符 位运算符对整数在内存…