Linux:线程池

日志

预备工作

下面开始,我们结合我们之前所做的所有封装,进行⼀个线程池的设计。在写之前,我们要做如下准备

  1. 准备线程的封装
  2. 准备锁和条件变量的封装
  3. 引入日志,对线程进行封装

对线程的封装:

Thread.hpp

复制代码
#ifndef __THREAD_HPP__
#define __THREAD_HPP__

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <functional>
#include <sys/syscall.h> 

#define get_lwp_id() syscall(SYS_gettid)

using func_t = std::function<void()>;
const std::string threadnamedefault = "None-Name";

class Thread
{
public:
    Thread(func_t func, const std::string &name = threadnamedefault)
        : _name(name),
          _func(func),
          _isrunning(false)
    {
        std::cout << "create thread obj success" << std::endl;
    }
    static void *start_routine(void *args)
    {
        Thread *self = static_cast<Thread *>(args);
        self->_isrunning = true;
        self->_lwpid = get_lwp_id();
        self->_func();
        pthread_exit((void *)0);
    }
    void Start()
    {
        int n = pthread_create(&_tid, nullptr, start_routine, this);
        if (n == 0)
        {
            std::cout << "run thread success" << std::endl;
        }
    }
    void Join()
    {
        if (!_isrunning)
            return;

        int n = pthread_join(_tid, nullptr);
        if (n == 0)
        {
            std::cout << "pthread_join success" << std::endl;
        }
    }
    ~Thread()
    {
    }
private:
    bool _isrunning;
    pthread_t _tid;
    pid_t _lwpid;
    std::string _name;
    func_t _func;
};

#endif

对锁的封装:

Mutex.hpp

复制代码
#pragma once
#include<pthread.h>
#include<mutex>

class Mutex
{
public:
Mutex()
{
    pthread_mutex_init(&_lock,nullptr);
}
void Lock()
{
    pthread_mutex_lock(&_lock);
}
void Unlock()
{
    pthread_mutex_unlock(&_lock);
}
~Mutex()
{
    pthread_mutex_destroy(&_lock);
}
pthread_mutex_t* Get()
{
    return &_lock;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex*lock):_lock(lock)
{
    _lock->Lock();
}
~LockGuard()
{
    _lock->Unlock();
}
    private:
    Mutex*_lock;
};

对条件变量的封装:

Cond.hpp

复制代码
#pragma once
#include <pthread.h>
#include <iostream>
#include "Mutex.hpp"

class Cond
{
public:
    Cond()
    {
        pthread_cond_init(&_cond, nullptr);
    }
    void NotifyAll()
    {
        int n = pthread_cond_broadcast(&_cond);
        (void)n;
    }
    void NotifyOne()
    {
        int n = pthread_cond_signal(&_cond);
        (void)n;
    }
    void Wait(Mutex &lock)
    {
        pthread_cond_wait(&_cond, lock.Get());
    }
    ~Cond()
    {
        pthread_cond_destroy(&_cond);
    }

private:
    pthread_cond_t _cond;
};

日志与策略模式

设计模式

前人针对一些常见的场景给出的一定的解决方案。比如日志中,前人已经规定好了设计模式,直接根据那个设计模式找思路即可,不用从头开始想。

日志的概念

日志 是程序运行时产生的文本记录,用于跟踪程序的执行状态、行为、错误和调试信息,帮助快速定位问题并支持程序员进行问题修复。

日志的构成要素

今天我们需要构建下面这个图片的日志格式:

里面的要素有:

  • 时间戳
  • 日志等级
  • 线程的id
  • 写日志的文件名
  • 文件行号
  • 日志内容

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。

代码

Logger.hpp

复制代码
#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_

#include "Mutex.hpp"
#include <string>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <iostream>
#include <filesystem> //C++17
#include <fstream>
#include <system_error>
#include<memory>
// 枚举
enum class LogLevel
{
    DEBUG,   // 调试信息
    INFO,    // 普通信息
    WARNING, // 警告
    ERROR,   // 错误
    FATAL    // 致命错误
};

// 把数字转化为字符
std::string LevelToString(LogLevel level)
{
    switch (level)
    {
    case LogLevel::DEBUG:
        return "DEBUG";
    case LogLevel::INFO:
        return "INFO";
    case LogLevel::WARNING:
        return "WARNING";
    case LogLevel::ERROR:
        return "ERROR";
    case LogLevel::FATAL:
        return "FATAL";
    default:
        return "Unknown";
    }
}
// 把时间戳格式化
std::string GetcurrTime()
{
    // 获取时间戳
    time_t currtime = time(nullptr);
    // 将时间戳转化为本地时间
    struct tm currtm;
    localtime_r(&currtime, &currtm);
    // 将时间转化为字符串
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
             currtm.tm_year + 1900,
             currtm.tm_mon+1,
             currtm.tm_mday,
             currtm.tm_hour,
             currtm.tm_min,
             currtm.tm_sec);
    return buffer;
}
// 刷新策略
// 基类
//设计模式:工厂模式
class LogStrategy
{
public:
    virtual ~LogStrategy() = default;
    virtual void SyncLog(const std::string &logmessage) = 0;
};
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {
    }
    void SyncLog(const std::string &logmessage) override
    {
        LockGuard lockguard(&_lock);
        std::cout << logmessage << std::endl;
    }

private:
    Mutex _lock;
};
std::string logdir = "log";      // 目录
std::string logname = "log.txt"; // 文件名
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(std::string dir = logdir, std::string name = logname)
        : _dir(dir), _filename(name)
    {
        LockGuard locckguard(&_lock);
        if (std::filesystem::exists(_dir))
        {
            return;
        }
        try
        {
            std::filesystem::create_directories(_dir);
        }
        catch (const std::filesystem::filesystem_error &e)
        {
            std::cerr << e.what() << "\r\n";
        }
    }
    void SyncLog(const std::string &logmessage) override
    {
        LockGuard lockguard(&_lock);
        std::string target = _dir;
        target += "/";
        target += _filename;
        std::ofstream out(target.c_str(), std::ios::app);
        if (!out.is_open())
        {
            return;
        }
        out << logmessage << "\n";
        out.close();
    }
    ~FileLogStrategy()
    {
    }

private:
    std::string _dir;
    std::string _filename;
    Mutex _lock;
};
class Logger
{
    public:
    Logger()
    {}
    void EnableFileLogStrategy()
    {
        _strategy=std::make_unique<FileLogStrategy>();//创建对象
    }
    void EnableConsoleLogStrategy()
    {
        _strategy=std::make_unique<ConsoleLogStrategy>();//创建对象
    }
    class LogMessage
    {
    public:
        LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
            : _curr_time(GetcurrTime()), _level(level), _pid(getpid()), _filename(filename), _line(line), _logger(logger)
        {
            std::stringstream ss;
            ss << "[" << _curr_time << "]"
               << "[" << LevelToString(_level) << "]"
               << "[" << _pid << "]"
               << "[" << _filename << "]"
               << "[" << _line << "]"
               << "-";
            _loginfo = ss.str();
        }
        template<typename T>
        LogMessage& operator<<(const T&info)
        {
            std::stringstream ss;
            ss<<info;
            _loginfo+=ss.str();
            return *this;
        }
        ~LogMessage()
        {
            //刷新缓冲区
            if(_logger._strategy)
            {
                _logger._strategy->SyncLog(_loginfo);
            }
        }
    private:
        std::string _curr_time; // 日志时间
        LogLevel _level;        // 日志等级
        pid_t _pid;             // 进程pid
        std::string _filename;  // 写日志的文件名
        int _line;              // 第几行
        std::string _loginfo;   // 保存一条完整的日志信息
        Logger &_logger;        // 提供刷新策略的具体做法
    };
    ~Logger()
    {}
    LogMessage operator()(LogLevel level,std::string filename,int line)
    {
        return LogMessage(level,filename,line,*this);
    }
    private:
    std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;

#define LOG(level) logger(level,__FILE__,__LINE__)
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#endif

测试:

main.cc

复制代码
#include<iostream>
#include"Logger.hpp"

int main()
{
    EnableConsoleLogStrategy();

    LOG(LogLevel::DEBUG) << "Test 1: simple string";

    LOG(LogLevel::DEBUG) << "Test 2: " << 123;

    LOG(LogLevel::DEBUG) << "Test 3: " << 3.14;
    
    LOG(LogLevel::DEBUG) << "Test 4: " << "hello" << ", " << 123;
    
    LOG(LogLevel::DEBUG) << "hello world" << ",3.14" << (int)123;
    return 0;
}
运行结果:
root@ubuntu:/home/slmx/d1/Logger# g++ -o main main.cc -lpthread -std=c++17
root@ubuntu:/home/slmx/d1/Logger# ./main
[2025-12-04 11:28:54][DEBUG][3270][main.cc][8]-Test 1: simple string
[2025-12-04 11:28:54][DEBUG][3270][main.cc][10]-Test 2: 123
[2025-12-04 11:28:54][DEBUG][3270][main.cc][12]-Test 3: 3.14
[2025-12-04 11:28:54][DEBUG][3270][main.cc][14]-Test 4: hello, 123
[2025-12-04 11:28:54][DEBUG][3270][main.cc][16]-hello world,3.14123

线程池设计

概念定义

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景

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

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

  3. 接受突发性的大量请求,但不足以使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池的种类

a. 创建固定数量线程池

循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中的任务接口

b. 浮动线程池

其他同上

此处,我们选择固定线程个数的线程池。

设计思路

线程安全的单例模式

概念

单例模式(Singleton Pattern)是一种创建型设计模式 ,确保一个类只有一个实例,并提供一个全局访问点。

实现方式

有两种实现方式:懒汉方式和饿汉方式。

用吃饭的例子举例:

懒汉就是吃完饭不洗碗等到下顿饭的时候再洗。即只要需要了才创建实例

饿汉就是吃完饭了就洗碗,等到下顿饭就可以直接开始吃了。即在还没被需要的时候,就已经创建了实例,可能会导致资源浪费。

饿汉方式实现单例模式

template<typename T>

class Singleton{

static T data;

public:

static T* GetInstance(){

return &data;

}

};

只需要通过这个Singleton这个包装类来使用T对象,则一个进程中只有一个T对象的实例。

懒汉方式实现单例模式

template <typename T>

class Singleton {

static T* inst;

public:

static T* GetInstance() {

if (inst == NULL) {

inst = new T();

}

return inst;

}

};

存在一个严重的问题, 线程不安全.

第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.

但是后续再次调用, 就没有问题了.

懒汉方式实现单例模式(线程安全版本)

ThreadPool.hpp

复制代码
#ifndef _PROCCESS_POOL_HPP_
#define _PROCESS_POOL_HPP_

#include <pthread.h>
#include <iostream>
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Logger.hpp"
#include <queue>
#include "Task.hpp"
#include"Thread.hpp"
//单例线程池------懒汉模式
const int defaultthreadnum = 3;
template <class T>
class ThreadPool
{
    private:
    bool QueueIsEmpty()
    {   
        return _q.empty();
    }   
    // 线程从任务队列中拿取任务
    void Routine(const std::string &name)
    {   
        while (true)
        {   
            T t;
            {   
                LockGuard lockguard(&_lock);
                while (QueueIsEmpty() && _is_running)
                {   
                    _wait_thread_num++;
                    _cond.Wait(_lock);
                    _wait_thread_num--;
                }   
                if (!_is_running && QueueIsEmpty())
                {   
                    LOG(LogLevel::INFO) << "线程池已退出&&任务队列为空," << name << "退出";
                    break;
                }   
                t = _q.front();
                _q.pop();
            }   
            t();//处理任务不需要在临界区
            LOG(LogLevel::DEBUG) << name << "handler task:" << t.ResultString();
        }   
    }   

public:
    ThreadPool(int threadnum = defaultthreadnum)
        : _threadnum(threadnum), _wait_thread_num(0), _is_running(false)
    {   
        for (int i = 0; i < _threadnum; i++)
        {   
            std::string name = "thread-" + std::to_string(i + 1); 
            // 方法1
            auto f = std::bind(&ThreadPool::Routine, this,name);
            _threads.emplace_back(f,name);
            // 方法2
            // _threads.emplace_back([this,name]()
            //                       { this->Routine(name); }, name); // emplace_back会自动构造Thread对象
        }
        LOG(LogLevel::INFO)<<"thread pool obj create success";
    }
    void Push(const T &t)
    {
        if(!_is_running)
        return;
        {
            LockGuard lockguard(&_lock);
            _q.push(t);
            if(_wait_thread_num)
            {
                _cond.NotifyAll();
            }
        }
    }
    void Wait()
    {
        for(auto&t:_threads)
        {
            t.Join();
        }
        LOG(LogLevel::INFO)<<"thread pool wait success";
    }
    void Start()
    {
        if(_is_running)
        {
            return ;
        }
        _is_running=true;
        for(auto &t:_threads)
        {
            //创建线程
            t.Start();
        }
    }
     // 核心思想:我们应该让线程走正常的唤醒逻辑退出
    // 线程池要退出
    // 1. 如果被唤醒 && 任务队列没有任务 = 让线程退出
    // 2. 如果被唤醒 && 任务队列有任务 = 线程不能立即退出,而应该让线程把任务处理完,在退出
    // 3. 线程本身没有被休眠,我们应该让他把他能处理的任务全部处理完成, 在退出
    void Stop()
    {
        if(!_is_running)
        {
            return;
        }
        _is_running=false;
        if(_wait_thread_num)//防止有些线程在等待任务而不知道停止信号
        {
            _cond.NotifyAll();
        }
    }
    ~ThreadPool()
    {}
    //DEBUG
    static std::string ToHex(ThreadPool<T>*inst)
    {
        char buffer[64];
        snprintf(buffer,sizeof(buffer),"%p",inst);
        return buffer;
    }
    static ThreadPool<T>*GetInstance()
    {
        if(_inst==NULL)
        {
            _singleton_lock.Lock();
            if(!_inst)
            {
                _inst=new ThreadPool<T>();
                LOG(LogLevel::DEBUG)<<"线程池单例首次被使用,创建并初始化,address:"<<ToHex(_inst);
                _inst->Start();
            }
        }
        return _inst;
    }
private:
    std::queue<T> _q;
    std::vector<Thread> _threads;
    int _threadnum;
    int _wait_thread_num;
    Mutex _lock;
    Cond _cond;
    bool _is_running;
    static Mutex _singleton_lock;
    //volatile static ThreadPool<T>* _inst;//设置关键字,防止被编译器优化 
    static ThreadPool<T>* _inst;
};
template <class T>
ThreadPool<T>*ThreadPool<T>::_inst=nullptr;
template <class T>
Mutex ThreadPool<T>::_singleton_lock;
#endif

main.cc

复制代码
#include"ThreadPool.hpp"
#include<ctime>
#include<unistd.h>
int main()
{
    srand(time(nullptr)^getpid());
    EnableConsoleLogStrategy();
    int cnt=20;
    while(cnt--)
    {   
        int x=rand()%10+1;
        int y=rand()%5+1;
        usleep(rand()%73);
        Task t(x,y);
        ThreadPool<Task>::GetInstance()->Push(t);
        sleep(1);
    }   
    ThreadPool<Task>::GetInstance()->Stop();
    ThreadPool<Task>::GetInstance()->Wait();
    return 0;
}

运行结果:

相关推荐
屿行屿行1 小时前
【Linux】音视频处理(gstreamer和ffmpeg的实际应用)
linux·ffmpeg·音视频·视频编解码
用户31187945592181 小时前
银河麒麟V10 申威架构 docker-compose rpm 包安装教程(附命令)
linux
峥嵘life1 小时前
Android EDLA 搭建Linux测试环境简介
android·linux·运维
小嘟嘟131 小时前
第3章 Shell 条件判断:解决 90% 的分支逻辑问题
linux·运维·shell
酷酷的佳1 小时前
用C语言写一个可以排序的程序
c++
19226381 小时前
西门子200Smart加Smart 1000 IE水处理程序画面案例。 采用成熟、可靠、先进、...
运维
⁤⁢初遇1 小时前
Linux------线程概念与控制
linux·运维·服务器
虹科数字化与AR1 小时前
安宝特新闻丨Vuzix展示LX1 AR智能眼镜与仓储自动化系统
运维·自动化·ar
MyFreeIT1 小时前
部署到Docker后,路径造成的异常
运维·docker·容器