Linux——日志的编写与线程池

目录

前言

一、日志的编写

二、线程池

1.线程池基本原理

2.线程池作用

3.线程池的实现


前言

学了很多线程相关的知识点,线程控制线程互斥线程同步,今天我们将他们做一个总结,运用所学知识写一个较为完整的线程池,同时把日志编写也学一下。

一、日志的编写

在企业开发过程中,经常会通过打印日志来查看当前项目的运行情况。写一个日志难度并不大,用到的都是之前学的知识,注释写得比较详细,代码如下。

Log.hpp

cpp 复制代码
#pragma once

#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{
    Debug = 0,
    Info,
    Warnig,
    Error,
    Fatal
};

enum{
    Screen = 10,
    OneFile,
    ClassFile
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warnig:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    
    default:
        return "Unkonw"; 
    }
}
 
const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";

class Log  
{
public:
    Log(int style = default_style,string filename = default_filename)
        :_style(style),_filename(filename)
    {
        mkdir(logdir.c_str(),0775);
    }

    //更改打印方式
    void Enable(int style)
    {
        _style = style;
    }

    //时间戳转化为年月日时分秒
    string GetTime()
    {
        time_t currtime = time(nullptr);
        struct tm* curr = localtime(&currtime);
        char time_buffer[128];
        snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
        curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);
        return time_buffer;
    }

    //写入到文件中
    void WriteLogToOneFile(const string& logname,const string& message)
    {
        FILE* fp = fopen(logname.c_str(),"a");
        if(fp==nullptr)
        {
            perror("fopen filed");
            exit(-1);
        }
        fprintf(fp, "%s\n", message.c_str());

        fclose(fp);
    }

    //打印日志
    void WriteLogToClassFile(const string& levelstr,const string& message)
    {
        string logname = logdir;
        logname+="/";
        logname+=_filename;
        logname+=levelstr;
        WriteLogToOneFile(logname,message);
    }

    void WriteLog(const string& levelstr,const string& message)
    {
        switch (_style) 
        {
        case Screen:
            cout<<message<<endl;//打印到屏幕中
            break;
        case OneFile:
            WriteLogToClassFile("all",message);//给定all,直接写到all里
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr,message);//写入levelstr里
            break;
        default:
            break;
        }
    }

    //打印日志
    void LogMessage(int level,const char* format,...)
    {
        char rightbuffer[1024];//处理消息
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        //现在args指向了可变参数部分
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        
        va_end(args);

        char leftbuffer[1024];//处理日志等级、pid、时间
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string loginfo = leftbuffer;
        loginfo+=rightbuffer;
        WriteLog(levelstr,loginfo);
    }

    //提供接口给运算符重载使用
    void _LogMessage(int level,char* rightbuffer)
    {
        char leftbuffer[1024];
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string messages = leftbuffer;
        messages+=rightbuffer;
        WriteLog(levelstr,messages);
    }

    //运算符重载
    void operator()(int level,const char* format,...)
    {
        char rightbuffer[1024];
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        va_end(args);
        _LogMessage(level,rightbuffer);
    }

    ~Log() 
    {}
private:
    int _style;
    string _filename;
};

Main.cc

cpp 复制代码
#include "Log.hpp"

int main()
{
    Log log;
    log.Enable(ClassFile);
    log(Debug, "%d %s %f", 10, "test", 3.14);
    log(Info, "%d %s %f", 10, "test", 3.14);
    log(Warnig, "%d %s %f", 10, "test", 3.14);
    log(Error, "%d %s %f", 10, "test", 3.14);
    log(Fatal, "%d %s %f", 10, "test", 3.14);
    log(Debug, "%d %s %f", 10, "test", 3.14);
    log(Info, "%d %s %f", 10, "test", 3.14);
    log(Warnig, "%d %s %f", 10, "test", 3.14);
    log(Error, "%d %s %f", 10, "test", 3.14);
    log(Fatal, "%d %s %f", 10, "test", 3.14);
    log(Debug, "%d %s %f", 10, "test", 3.14);
    log(Info, "%d %s %f", 10, "test", 3.14);
    log(Warnig, "%d %s %f", 10, "test", 3.14);
    log(Error, "%d %s %f", 10, "test", 3.14);
    log(Fatal, "%d %s %f", 10, "test", 3.14);
}

数据就被我们分门别类的写入到了文件中,后面查询起来就很方便。

二、线程池

之前我们还学习过生产者消费者模型,创建一批线程去生产,还有一批线程去消费。线程池也类似于如此。只不过生产是主线程自己做。有了任务他只需要将内容分配给已经被创建好进程,让这些进程去消费就行了,而这些被提前创建好的进程,就叫做进程池

1.线程池基本原理

  • 线程池由一组预先创建的线程组成,这些线程等待接收并执行任务。
  • 当需要执行任务时,而且线程池中有空闲线程时,任务被分配给其中一个空闲线程执行。
  • 如果没有空闲线程,任务将被放入任务队列中等待执行,直到有线程空闲为止。

2.线程池作用

  • 降低了线程创建和销毁的开销:线程的创建和销毁是比较昂贵的操作,线程池可以避免频繁地创建和销毁线程。
  • 提高了性能:通过重用线程,可以减少线程的上下文切换和内存占用,提高了系统的整体性能。
  • 控制并发度:可以限制并发执行的任务数量,防止系统资源被耗尽。

3.线程池的实现

我们需要如下变量:

  1. 一个任务队列,用于存储待执行的任务。
  2. 一个容器,来存放线程。
  3. 整形变量:存放线程最多的数量
  4. 保护临界资源:互斥锁
  5. 通知线程去处理任务:条件变量

具体代码如下

LockGuard.hpp 不生产锁,做线程的守护者,作用域到了自动释放锁

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

// 不定义锁,外部会传递锁
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void UnLock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        : _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;
};

Thread.hpp 模拟C++实现的线程,封装了一下

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T&)>;

template<class T>
class Thread
{
public:
    Thread(const std::string &threadname, func_t<T> func, T &data)
    :_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
    {}

    // 不加static会有this指针,无法调用pthread_creadte
    static void *ThreadRoutine(void *args) // 类内方法,
    {
        // (void)args; // 仅仅是为了防止编译器有告警
        Thread *ts = static_cast<Thread *>(args);

        ts->_func(ts->_data);

        return nullptr;
    }

    //运行线程
    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
        if(n == 0) 
        {
            _isrunning = true;
            return true;
        }
        else return false;
    }

    //等待线程
    bool Join()
    {
        if(!_isrunning) return true;
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }
    std::string ThreadName()
    {
        return _threadname;
    }
    bool IsRunning()
    {
        return _isrunning; 
    }
    ~Thread()
    {}
private:
    pthread_t _tid;
    std::string _threadname;
    bool _isrunning;
    func_t<T> _func;
    T _data;
};

Log.hpp 前面写的日志

cpp 复制代码
#pragma once

#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{
    Debug = 0,
    Info,
    Warnig,
    Error,
    Fatal
};

enum{
    Screen = 10,
    OneFile,
    ClassFile
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warnig:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    
    default:
        return "Unkonw"; 
    }
}
 
const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";

class Log  
{
public:
    Log(int style = default_style,string filename = default_filename)
        :_style(style),_filename(filename)
    {
        mkdir(logdir.c_str(),0775);
    }

    //更改打印方式
    void Enable(int style)
    {
        _style = style;
    }

    //时间戳转化为年月日时分秒
    string GetTime()
    {
        time_t currtime = time(nullptr);
        struct tm* curr = localtime(&currtime);
        char time_buffer[128];
        snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
        curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);
        return time_buffer;
    }

    //写入到文件中
    void WriteLogToOneFile(const string& logname,const string& message)
    {
        FILE* fp = fopen(logname.c_str(),"a");
        if(fp==nullptr)
        {
            perror("fopen filed");
            exit(-1);
        }
        fprintf(fp, "%s\n", message.c_str());

        fclose(fp);
    }

    //打印日志
    void WriteLogToClassFile(const string& levelstr,const string& message)
    {
        string logname = logdir;
        logname+="/";
        logname+=_filename;
        logname+=levelstr;
        WriteLogToOneFile(logname,message);
    }

    void WriteLog(const string& levelstr,const string& message)
    {
        switch (_style) 
        {
        case Screen:
            cout<<message<<endl;//打印到屏幕中
            break;
        case OneFile:
            WriteLogToClassFile("all",message);//给定all,直接写到all里
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr,message);//写入levelstr里
            break;
        default:
            break;
        }
    }

    //打印日志
    void LogMessage(int level,const char* format,...)
    {
        char rightbuffer[1024];//处理消息
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        //现在args指向了可变参数部分
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        
        va_end(args);

        char leftbuffer[1024];//处理日志等级、pid、时间
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string loginfo = leftbuffer;
        loginfo+=rightbuffer;
        WriteLog(levelstr,loginfo);
    }

    //提供接口给运算符重载使用
    void _LogMessage(int level,char* rightbuffer)
    {
        char leftbuffer[1024];
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string messages = leftbuffer;
        messages+=rightbuffer;
        WriteLog(levelstr,messages);
    }

    //运算符重载
    void operator()(int level,const char* format,...)
    {
        char rightbuffer[1024];
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        va_end(args);
        _LogMessage(level,rightbuffer);
    }

    ~Log() 
    {}
private:
    int _style;
    string _filename;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.Enable(ClassFile); 
    }
    ~Conf()
    {}
};

Conf conf;

ThreadPool.hpp 线程池的实现

cpp 复制代码
#pragma once

#include <pthread.h>
#include <vector>
#include <functional>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace std;

static const int default_num = 5;

class ThreadData
{
public:
    ThreadData(string name)
        :thread_name(name)
    {}
    string thread_name;
};

template <class T>
class ThreadPool
{
public:
    ThreadPool(int thread_num = default_num)
        : _thread_num(thread_num)
    {
        pthread_mutex_init(&_mutex, nullptr); // 初始化
        pthread_cond_init(&_cond, nullptr);

        // 创建指定个数的线程
        for (int i = 0; i < _thread_num; i++)
        {
            string thread_name = "thread_";
            thread_name += to_string(i + 1);

            ThreadData td(thread_name);//ThreadData为线程数据类型
            
            Thread<ThreadData> t(thread_name, bind(&ThreadPool<T>::ThreadRun, this, 
            placeholders::_1), td);
            _threads.emplace_back(t);

            lg(Info,"%s 被创建...",thread_name.c_str());//写入
        }
    }

    //线程运行
    bool Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
            lg.LogMessage(Info,"%s 正在运行!",thread.ThreadName().c_str());
        }
    }

    //线程条件变量等待
    void ThreadWait(ThreadData &td)
    {
        lg.LogMessage(Debug,"没有任务,%s休眠了",td.thread_name.c_str());
        pthread_cond_wait(&_cond,&_mutex);
    }

    //线程条件变量唤醒
    void ThreadWakeUp()
    {
        pthread_cond_signal(&_cond);
    }

    //执行任务
    void ThreadRun(ThreadData &td)
    {
        while (1)
        {
            T t;
            // 取出任务
            {
                LockGuard lockguard(&_mutex);//代码块中自动加锁与解锁
                while (_q.empty())
                {
                    ThreadWait(td);
                    lg.LogMessage(Debug,"有任务了,%s去执行任务了",td.thread_name.c_str());
                }
                t = _q.front();
                _q.pop();
            }
            //处理任务 我们通过打印消息来模拟任务
            // cout<<t<<endl;
            lg.LogMessage(Debug,"%s 计算结果为:%d",td.thread_name.c_str(),t);
        }
    }

    //将任务放到队列中
    void Push(const T &in)
    {
        {
            LockGuard lockguard(&_mutex);
            _q.push(in);
        }
        lg.LogMessage(Debug,"任务push成功,任务是: %d",in);
        ThreadWakeUp();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex); // 销毁
        pthread_cond_destroy(&_cond);
    }

    //进程等待
    void Wait()
    {
        for (auto &thread : _threads)
        {
            thread.Join();
        }
    }

private:
    queue<T> _q;
    vector<Thread<ThreadData>> _threads;
    int _thread_num;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

Main.cc

cpp 复制代码
#include <memory>
#include "Threadpool.hpp"


int main()
{
    unique_ptr<ThreadPool<int>> tp(new ThreadPool<int>());
    tp->Start();
    int i = 1;
    while(true)
    {
        //放入数据模拟任务
        tp->Push(i++);
        sleep(1);
    }
    tp->Wait();
    return 0;
}

执行后,就将内容写入到了日志里面。

相关推荐
是店小二呀3 分钟前
【Linux】Linux开发利器:make与Makefile自动化构建详解
linux·运维·自动化
qq_4335545410 分钟前
C++ 面向对象编程:递增重载
开发语言·c++·算法
易码智能18 分钟前
【EtherCATBasics】- KRTS C++示例精讲(2)
开发语言·c++·kithara·windows 实时套件·krts
ཌ斌赋ད25 分钟前
FFTW基本概念与安装使用
c++
带多刺的玫瑰31 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
巫师不要去魔法部乱说42 分钟前
PyCharm专项训练5 最短路径算法
python·算法·pycharm
BUG 4041 小时前
LINUX--shell
linux·运维·服务器
薄荷故人_1 小时前
从零开始的C++之旅——红黑树封装map_set
c++
菜鸟小白:长岛icetea1 小时前
Linux零基础速成篇一(理论+实操)
linux·运维·服务器
深海的鲸同学 luvi1 小时前
【HarmonyOS NEXT】hdc环境变量配置
linux·windows·harmonyos