wordpress 建站插件,如何不花钱建设网站,天河网站建设网络推广,优秀设计案例作品Linux学习笔记---018 Linux之线程池、进程池知识储备1、线程池1.1、池化技术1.1.1、定义与原理1.1.2、优点1.1.3、应用场景 1.2、线程池的特点与优势1.3、线程池的适用场景1.4、线程池的实现 2、进程池2.1、定义和基本概念2.2、进程池的特点与优势2.3、进程池的适用场景#x… Linux学习笔记---018 Linux之线程池、进程池知识储备1、线程池1.1、池化技术1.1.1、定义与原理1.1.2、优点1.1.3、应用场景 1.2、线程池的特点与优势1.3、线程池的适用场景1.4、线程池的实现 2、进程池2.1、定义和基本概念2.2、进程池的特点与优势2.3、进程池的适用场景 3、单例模式3.1、“饿汉”3.2、“懒汉” 4、死锁问题4.1、死锁的定义4.2、死锁的产生条件4.3、死锁的原因4.4、死锁的解决方式4.5、小结 5、 可重入 VS 线程安全5.1、可重入5.2、线程安全5.3、关系与区别 6、volatile关键字6.1、volatile关键字的作用6.2、volatile样例 7、日志的重要性7.1、日志文件概述7.2、常见日志文件及作用7.3、日志管理工具7.4、日志的查看与分析7.5、简易日志的实现7.6、日志的维护与优化 Linux之线程池、进程池知识储备
前言 前篇开始进行了解学习Linux线程、进程、多线程、多进程等相关内容接下来学习关于Linux线程池、进程池等知识深入地了解这个强大的开源操作系统。 /知识点汇总/
1、线程池 线程池是一种基于池化技术的并发框架它预先创建并管理一组线程用于执行提交给它的任务。 线程池中的线程可以重复利用减少了线程的创建和销毁开销提高了系统的响应速度和吞吐量。 1.1、池化技术 池化技术Pooling Technique是一种广泛应用于软件开发、系统设计以及深度学习中的资源管理策略。其核心理念是通过复用预先创建和初始化的资源来提升系统性能、效率和稳定性。 1.1.1、定义与原理 池化技术指的是在系统运行前或运行时预先分配并维护一组资源如线程、数据库连接、内存块等这些资源被组织成一个“池”。当系统需要这些资源时不是每次都重新创建新的实例而是从池中获取一个已存在的实例进行使用。使用完毕后实例会被归还到池中以便后续的请求可以重复利用。 1.1.2、优点 1.资源节约 减少资源创建与销毁的开销。例如创建新线程、打开数据库连接等操作可能涉及耗时的系统调用而池化技术通过复用已有资源来避免这些开销。 2.提高响应速度 通过预先初始化好的资源和重用现有资源避免了每次请求时的初始化延迟从而提高了系统的响应速度。 3.控制资源使用量 限制资源的最大使用数量防止资源耗尽确保系统的稳定运行。 4.增强系统性能和稳定性 通过管理资源的生命周期监控可以及时调整资源分配满足系统需求并更好地处理资源泄露等问题。 1.1.3、应用场景 1.线程池 管理一组可用的线程以高效地执行并发任务。线程池减少了线程的创建和销毁开销提高了系统对并发请求的响应能力。 2.数据库连接池 管理数据库连接资源通过复用连接来减少连接创建和销毁的开销提高系统对数据库资源的访问性能。 3.内存池 将一块连续的内存分割成多个固定大小的对象块通过对这些对象进行复用减少内存分配与释放的频率提高内存使用效率。 4.连接池 不仅限于数据库连接还包括如Redis连接池、HTTP连接池等用于管理各种网络连接资源。 5.对象池 管理一组可复用的对象实例以减少对象的创建和销毁开销提高系统的性能和资源利用率。 6.缓存池 将常用的计算结果、数据或资源存储在内存中以加快对这些数据的访问速度。 1.2、线程池的特点与优势 1.资源共享 线程池中的线程共享相同的地址空间和资源使得线程间的通信和同步变得容易。 2.减少开销 线程的创建和销毁开销相对较小线程池通过重用线程减少了这些开销。 3.提高响应速度 线程池可以快速地响应大量的并发请求因为它可以立即分配一个空闲线程来处理新任务。 4.易于管理 线程池提供了统一的接口来管理线程的生命周期和任务执行简化了并发编程的复杂性。 1.3、线程池的适用场景 1.IO密集型任务 如Web服务器处理HTTP请求数据库操作等。这些任务大部分时间都在等待IO操作完成线程池可以充分利用CPU资源来并发处理这些任务。 2.高并发场景 当需要处理大量并发请求时线程池可以快速地分配线程来处理这些请求提高系统的响应速度和吞吐量。 3.计算密集型任务但非极端情况 虽然线程池在处理计算密集型任务时可能不如进程池高效因为线程间的切换开销和共享资源竞争但在非极端情况下线程池仍然可以胜任。 1.4、线程池的实现 ThreadPool.hpp #pragma once#include iostream
#include unistd.h
#include string
#include vector
#include queue
#include functional
#include Thread.hppusing namespace ThreadMoudle;static const int gdefaultnum 5;void test()
{while (true){std::cout hello world std::endl;sleep(1);}
}template typename T
class ThreadPool
{
private:void LockQueue(){pthread_mutex_lock(_mutex);}void UnlockQueue(){pthread_mutex_unlock(_mutex);}void Wakeup(){pthread_cond_signal(_cond);}void WakeupAll(){pthread_cond_broadcast(_cond);}void Sleep(){pthread_cond_wait(_cond, _mutex);}bool IsEmpty(){return _task_queue.empty();}void HandlerTask(const std::string name) // this处理任务{while (true){// 取任务LockQueue();while (IsEmpty() _isrunning){_sleep_thread_num;Sleep();//休眠_sleep_thread_num--;}// 判定这种情况才退出if (IsEmpty() !_isrunning){std::cout name quit std::endl;UnlockQueue();break;}// 有任务T t _task_queue.front();_task_queue.pop();UnlockQueue();// 处理任务t(); // 处理任务此处不用/不能在临界区中处理std::cout name : t.result() std::endl;}}public:ThreadPool(int thread_num gdefaultnum) : _thread_num(thread_num), _isrunning(false), _sleep_thread_num(0){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);}void Init(){//强关联绑定,且placeholders自动推导name参数func_t func std::bind(ThreadPool::HandlerTask, this, std::placeholders::_1);//绑定for (int i 0; i _thread_num; i){std::string threadname thread- std::to_string(i 1);_threads.emplace_back(threadname, func);//内部构造减少临时拷贝}}void Start(){_isrunning true;//启动线程池for (auto thread : _threads){thread.Start();}}void Stop(){LockQueue();_isrunning false;WakeupAll();UnlockQueue();}void Equeue(const T in){LockQueue();if (_isrunning)//运行状态才进行操作{_task_queue.push(in);//入队if (_sleep_thread_num 0)//有线程才唤醒Wakeup();//唤醒}UnlockQueue();}~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}private:int _thread_num;std::vectorThread _threads;std::queueT _task_queue;bool _isrunning;int _sleep_thread_num;//计数休眠线程个数//加锁pthread_mutex_t _mutex;//唤醒pthread_cond_t _cond;
};Task.hpp #pragma once/
//方法三function
/**/
#include iodtream
#include functionalusing Task_t std::functionvoid();//函数对象返回值为void,参数为空的函数类型对象
//等价
//typedef std::functionvoid() Task_t;void Download()
{std::cout 我是一个下载任务 std::endl;
}/
//方法二类
/*
class Task
{Task(){}Task(int x,int y):_x(x),_y(y){}void Excute(){_result _x _y;}void operator()(){Excute();}std::string debug(){std::string msg std::to_string(_x) std::to_string(_y) ?;return msg;}std::string result(){std::string msg std::to_string(_x) std::to_string(_y) std::to_string(_result);return msg;}
private:int _x;int _y;int _result;
};
*/
/**/
#pragma once#includeiostream
#includefunctional// typedef std::functionvoid() task_t;
// using task_t std::functionvoid();// void Download()
// {
// std::cout 我是一个下载的任务 std::endl;
// }// 要做加法
class Task
{
public:Task(){}Task(int x, int y) : _x(x), _y(y){}void Excute(){_result _x _y;}void operator ()(){Excute();}std::string debug(){std::string msg std::to_string(_x) std::to_string(_y) ?;return msg;}std::string result(){std::string msg std::to_string(_x) std::to_string(_y) std::to_string(_result);return msg;}private:int _x;int _y;int _result;
};main.cc #include ThreadPool.hpp
#include Task.hpp
//#include memoryint main()
{//std::unique_ptrThreadPool tp std::make_uniqueThreadPool();//C14 智能指针//ThreadPoolint* tp new ThreadPoolint();ThreadPoolTask* tp new ThreadPoolTask();tp-Init();tp-Start();int cnt 10;while (cnt--){//不断的向线程池推送任务sleep(1);Task t(1, 1);tp-Equeue(t);sleep(1);std::cout cnt cnt-- std::endl;}tp-Stop();std::cout thread pool stop std::endl;sleep(10);return 0;
}2、进程池
2.1、定义和基本概念 进程池与线程池类似但它管理的是一组进程而不是线程。 每个进程都有自己独立的地址空间和资源因此进程间的通信和同步相对复杂但提供了更好的隔离性和安全性。 2.2、进程池的特点与优势 1.高隔离性 每个进程都有自己独立的地址空间和资源因此进程间的隔离性较好一个进程的崩溃不会影响其他进程。 2.安全性 由于进程间的隔离性进程池可以提供更高的安全性防止恶意代码或错误操作对其他进程造成影响。 3.适用性强 对于需要独立运行的任务或需要较高隔离性的场景进程池是更好 的选择。
2.3、进程池的适用场景 **1.CPU密集型任务 ** 当任务主要是计算密集型时进程池可以提供更好的性能因为每个进程都有自己独立的CPU资源减少了线程间的切换开销和共享资源竞争。 **2.高隔离性需求 ** 当任务之间需要较高的隔离性时如避免一个任务的崩溃影响其他任务时进程池是更好的选择。 3.安全性要求高的场景 如处理敏感数据或执行高风险操作时进程池可以提供更高的安全性保障。 3、单例模式 Linux单例模式是一种常用的软件设计模式**旨在确保一个类在系统中只有一个实例并提供一个全局访问点来获取这个实例。**单例模式可以分为饿汉式和懒汉式两种实现方式它们的主要区别在于实例的创建时机。 3.1、“饿汉”
1.特点 系统一运行就立即创建实例实例的创建是线程安全的。 实例在类加载时就完成了初始化所以类加载较慢但获取对象的速度快。 2.实现方式 饿汉式单例模式通常通过静态初始化块或直接在静态变量初始化时创建实例。这种方式保证了单例的唯一性并且由于是静态初始化因此也是线程安全的。 class Singleton {
private: static Singleton instance; // 静态实例类加载时就初始化 // 私有化构造函数和析构函数防止外部创建和销毁实例 Singleton() {} ~Singleton() {} public: // 提供一个静态方法返回实例 static Singleton GetInstance() { return instance; } // 禁止拷贝和赋值 Singleton(const Singleton) delete; Singleton operator(const Singleton) delete;
}; // 静态实例初始化
Singleton Singleton::instance;注意 在C中如果全局对象之间有依赖关系可能会导致构造顺序问题。此外由于实例在类加载时就创建可能会浪费资源尤其是当实例很大且长时间不被使用时。 3.2、“懒汉”
1.特点 实例在第一次被使用时创建即“延时加载”。 在多线程环境下需要加锁来保证线程安全否则可能创建多个实例。 2.实现方式 懒汉式单例模式在第一次调用GetInstance方法时创建实例。为了实现线程安全可以使用互斥锁如std::mutex来保护实例的创建过程。 #include mutex class Singleton {
private: static Singleton* instance; // 静态指针指向类的唯一实例 static std::mutex mtx; // 互斥锁 // 私有化构造函数和析构函数 Singleton() {} ~Singleton() {} public: // 提供一个静态方法返回实例 static Singleton* GetInstance() { if (instance nullptr) { std::lock_guardstd::mutex lock(mtx); // 加锁 if (instance nullptr) { // 双重检查锁定 instance new Singleton(); } } return instance; } // 禁止拷贝和赋值 Singleton(const Singleton) delete; Singleton operator(const Singleton) delete;
}; // 静态成员初始化
Singleton* Singleton::instance nullptr;
std::mutex Singleton::mtx;注意 懒汉式单例模式在多线程环境下需要加锁来保证线程安全但加锁会带来一定的性能开销。此外懒汉式单例模式在第一次使用实例时才创建因此可以提高程序启动速度并减少不必要的资源浪费。但是如果实例创建过程较为复杂或耗时较长可能会影响程序的首次响应时间。 4、死锁问题 死锁是指在一组进程中各个进程均占有不会释放的资源但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态称为死锁即: 长时间代码不被推进。 Linux死锁问题是一个在多进程或多线程环境中常见的复杂问题它指的是两个或多个进程或线程在执行过程中因互相持有对方所需的资源而又都等待对方释放资源导致它们都无法继续执行下去的一种僵局状态。 4.1、死锁的定义 在Linux操作系统中死锁是指一组进程或线程中的每一个进程或线程都在等待仅由该组进程中的其他进程或线程才能引发的事件从而导致它们都无法继续执行。这种情况发生时进程或线程将无法进行下去无法释放资源也无法获取需要的资源进而导致系统无法继续运行。 4.2、死锁的产生条件
死锁通常发生在多进程或多线程环境中当满足以下四个条件时就可能发生死锁 1.互斥条件 一个资源只能被一个进程线程访问即资源独占。 2.占有且等待 进程线程在占有一个资源时可以请求其他资源。 3.不可剥夺条件 一个资源只能由其持有者释放不能强行剥夺。 4.循环等待条件 多个进程线程之间形成一种循环等待资源的关系每个进程线程等待下一个进程线程所持有的资源。 4.3、死锁的原因
Linux中产生死锁的原因主要有以下几种 1.竞争不可抢占资源 如共享文件时引起死锁当两个或多个进程都试图访问对方已占用的资源时就可能发生死锁。 2.竞争可消耗资源 如消息传递中的死锁当进程间通过消息传递进行通信时如果它们都以特定的顺序发送和接收消息且都先等待接收消息而不发送就可能发生死锁。 3.进程推进顺序不当 进程在运行过程中请求和释放资源的顺序不当也可能导致死锁。 4.4、死锁的解决方式
为了避免和解决死锁问题可以采取以下几种方法 1.预防死锁 通过设置某些限制条件以破坏产生死锁的四个必要条件中的一个或几个来防止发生死锁。例如通过资源预分配、资源有序性等方法来降低死锁的可能性。 2.避免死锁 在资源的动态分配过程中使用某种方法去防止系统进入不安全状态从而避免了死锁的发生。例如使用银行家算法来动态检查资源分配的安全性。 3.检测死锁 允许系统运行过程中发生死锁但通过系统所设置的检测机构及时检测出死锁的发生并精确地确定与死锁有关的进程和资源。 4.解除死锁 一旦检测到死锁就采取适当措施从系统中消除死锁。常用的方法是撤销或者挂起一些进程以便于释放出一些资源再将它分配给已经处于阻塞的进程使其转换为就绪状态可以继续运行。 4.5、小结
死锁的四个条件 1.互斥条件一个资源每次只能被一个执行流使用 2.请求与保持条件一个执行流因请求资源而阻塞时对已获得的资源保持不放。 3.不剥夺条件一个执行流已获得的资源在未使用完之前不能强行剥夺 4.循环等待条件若干执行流之间形成一种头尾相接的循环等待资源的关系。 避免死锁 1.破坏死锁的四个必要条件 2.加锁顺序一致 3.避免锁未释放的场景 4.资源一次性分配 5、 可重入 VS 线程安全
5.1、可重入 可重入性通常指的是一个函数或程序段在被多个线程或单个线程多次调用时能够正确地执行而不会产生不可预测的结果即使这些调用是同时进行的并发执行。 在C/C等语言中一个函数如果满足以下条件则可以被认为是可重入的 1.不修改全局变量或者至少是以一种线程安全的方式修改全局变量。 2.不依赖于静态局部变量因为静态局部变量在函数的不同调用之间保持其值。 3.不调用任何不可重入的函数。 值得注意的是 可重入性通常关注的是函数内部的状态管理而不直接涉及多线程间的同步问题。 5.2、线程安全 线程安全指的是一个函数或程序段在多个线程同时执行时能够正确地执行而不会产生不可预测的结果。 线程安全通常需要确保 1.对共享资源的访问是同步的 这通常通过互斥锁mutexes、信号量semaphores或其他同步机制来实现。 2.避免了竞态条件 即多个线程尝试同时修改同一资源时可能导致的问题。 3.正确处理了所有可能的并发访问情况。 线程安全的概念比可重入更广泛因为它涉及到多线程之间的交互和同步。一个线程安全的函数或程序段可能包括不可重入的函数但通过适当的同步机制来保证整体的线程安全性。 5.3、关系与区别 1.可重入是线程安全的一个必要条件 一个线程安全的函数或程序段必须是可重入的但可重入的函数或程序段不一定是线程安全的特别是当它们访问共享资源时。 2.关注点不同 可重入性主要关注函数内部的状态管理而线程安全性则关注多线程之间的交互和同步。 小结 1.线程安全 多个线程并发同一段代码时不会出现不同的结果。 2. 重入 指对同一个函数被不同的执行流调用当前一个流程还没完就有其他的执行流再次进入我们称之为重入。且一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数。 3.结论 如果一个函数是可重入的那么多线程调用这个可重入函数时多线程也是安全的 反过来多线程能调用可重入。那么整个代码就是安全的。 线程安全不一定是可重入的可重入函数则一定是线程安全的。 6、volatile关键字 C中的volatile关键字是一个类型修饰符用于告诉编译器该变量的值可能会在程序的控制之外被改变。这通常用于多线程编程、硬件访问如内存映射的I/O寄存器或中断服务例程中其中变量的值可能会因为外部事件如硬件中断或另一个线程的执行而突然改变。 6.1、volatile关键字的作用
1.防止编译器过度优化 编译器在编译代码时会尝试通过优化来提高程序的执行效率。对于它认为在两次读取之间不会改变的变量编译器可能会将其值缓存在寄存器中而不是每次都从内存中读取。然而如果这样的变量被volatile修饰编译器就会知道这个变量的值可能会突然改变因此它不会对这个变量进行这样的优化。 2.保证内存可见性 在多线程环境中一个线程对volatile变量的修改对其他线程是可见的。然而需要注意的是volatile并不保证操作的原子性也不提供任何形式的同步机制如互斥锁。 6.2、volatile样例 下面是一个简单的C多线程示例其中使用了volatile关键字来确保一个线程能够检测到另一个线程对共享变量的修改。但是请注意这个示例并不是最佳实践因为volatile并不足以保证线程安全。在实际应用中应该使用更高级的同步机制如互斥锁std::mutex或原子操作std::atomic。 #include iostream
#include thread
#include chrono volatile bool running true; // 使用volatile修饰共享变量 void threadFunction() { while (running) { // 执行一些操作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作 } std::cout Thread has stopped. std::endl;
} int main() { std::thread t(threadFunction); // 做一些工作 std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟主线程的工作 running false; // 通知线程停止 t.join(); // 等待线程结束 return 0;
}在这个例子中running变量被声明为volatile以确保当主线程将其设置为false时这个修改对threadFunction中的线程是可见的。然而由于volatile不保证操作的原子性如果running的读取和写入不是原子的在大多数现代处理器上单个字节的读写通常是原子的但这不是由C标准保证的那么仍然可能会遇到竞态条件。 此外即使running的读取和写入是原子的volatile也不足以防止指令重排序等更复杂的并发问题。因此在需要确保线程安全的情况下应该使用更强大的同步机制。 注意 在C11及更高版本中推荐使用std::atomic来代替volatile进行线程间的同步和通信。std::atomic提供了必要的原子性和内存顺序保证以安全地在多线程环境中使用共享变量。 volatile主要用于与硬件直接交互的场景或者当你确信编译器优化会导致问题时。在大多数多线程编程场景中应该优先考虑使用std::atomic、std::mutex、std::condition_variable等同步机制。 7、日志的重要性 Linux日志是Linux系统中非常重要的组成部分它们记录了系统的运行情况、错误信息、用户活动等重要信息对于系统管理和故障排查具有不可替代的作用。 7.1、日志文件概述 Linux系统中的日志文件通常存储在/var/log目录下该目录下包含了多种不同类型的日志文件用于记录不同类型的信息。例如/var/log/messages记录了系统的各种事件和错误信息/var/log/secure记录了用户的登录、认证和授权信息/var/log/httpd/access_log和/var/log/httpd/error_log则分别记录了Apache HTTP服务器的访问日志和错误日志等。 7.2、常见日志文件及作用
1.系统日志
nameValue/var/log/messages记录了系统的各种事件和错误信息包括系统启动、登录、网络连接、内核错误等/var/log/dmesg记录了系统引导过程中的信息包括硬件检测、内核启动等/var/log/boot.log记录了系统在引导过程中发生的事件与/var/log/dmesg类似但可能包含更详细的启动信息
2.用户日志
nameValue/var/log/secure记录了用户的登录、认证和授权信息包括成功和失败的登录尝试、sudo或su命令的使用等/var/log/lastlog记录了每个用户最后一次成功登录的信息如登录时间、IP地址等/var/log/wtmp记录了用户登录、注销、系统启动等信息可以使用last命令查看信息/var/log/btmp记录了失败的登录尝试信息可以使用lastb命令查看
3.程序日志 应用程序通常会将日志信息写入到/var/log目录下的特定文件中文件名和位置取决于应用程序的配置。例如Apache HTTP服务器的访问日志和错误日志分别存储在/var/log/httpd/access_log和/var/log/httpd/error_log中。 其他常见的程序日志包括Nginx的/var/log/nginx/access.log和/var/log/nginx/error.logMySQL的/var/log/mysql/error.log等。 4.安全日志
nameValue/var/log/audit/audit.log记录了安全事件、安全漏洞、攻击尝试等信息需要启用审计服务auditd才能生成此日志文件
7.3、日志管理工具 Linux系统提供了多种日志管理工具用于帮助用户查看、分析和管理日志文件。 常用的工具有
nameValuetail用于查看文件的末尾内容特别是与-f选项结合使用时可以实时监控日志文件的更新cat用于显示整个文件的内容虽然对于大型日志文件来说可能不太实用但在处理小型文件或需要查看整个文件内容时非常有用grep用于在文件中搜索包含特定模式的行是日志分析中最常用的命令之一less提供了一个分页查看文件内容的界面允许用户向前和向后浏览文件搜索文本等awk一个强大的文本处理工具可以用于格式化文本文件和提取数据sed一个流编辑器用于对文本文件进行基本的文本转换
7.4、日志的查看与分析
在Linux系统中查看和分析日志文件的常用方法包括 1.使用tail、cat、grep等命令直接查看日志文件的内容。 2.使用less或more命令分页查看大型日志文件的内容。 3.使用awk、sed等文本处理工具对日志文件进行格式化、提取和转换操作。 4.结合使用多个命令和管道符|实现复杂的日志分析和过滤操作。 7.5、简易日志的实现
获取时间的函数 int gettimeofday(struct timeval* tv,struct timezone* tz);int settimeofday(const struct timeval* tv,const struct timezone* tz);time_t time(time_t* tloc);//time_t时间戳转换时间戳为年月日制 struct tm* loacltime(const time_t* timep);处理可变参数常用vsnprintf #include stdarg.hint vsnprintf(char* str,size_t size,const char* format,va_list ap);补充 实参传递到形参是把形参入栈从有向左入栈的。 比如可变参数的样例。 va_list ap;就是可变参数指针 va_start(ap,num);初始化num是距离可变参数左边最近的参数 … ca_end(ap);释放 Log.hpp程序实现 #pragma once#include iostream
#include sys/types.h
#include unistd.h
#include ctime
#include cstdarg
#include fstream
#include cstring
#include pthread.h
#include LockGuard.hppnamespace log_ns
{enum{DEBUG 1,INFO,WARNING,ERROR,FATAL};std::string LevelToString(int level)//日志等级{switch (level){case DEBUG:return DEBUG;case INFO:return INFO;case WARNING:return WARNING;case ERROR:return ERROR;case FATAL:return FATAL;default:return UNKNOWN;}}std::string GetCurrTime()//获取时间{time_t now time(nullptr);struct tm* curr_time localtime(now);//转时间制char buffer[128];snprintf(buffer, sizeof(buffer), %d-%02d-%02d %02d:%02d:%02d,curr_time-tm_year 1900,curr_time-tm_mon 1,curr_time-tm_mday,curr_time-tm_hour,curr_time-tm_min,curr_time-tm_sec);return buffer;}class logmessage{public:std::string _level;pid_t _id;std::string _filename;int _filenumber;std::string _curr_time;std::string _message_info;};#define SCREEN_TYPE 1
#define FILE_TYPE 2const std::string glogfile ./log.txt;pthread_mutex_t glock PTHREAD_MUTEX_INITIALIZER;// log.logMessage(, 12, INFO, this is a %d message ,%f, %s hellwrodl, x, , , );class Log{public:Log(const std::string logfile glogfile) : _logfile(logfile), _type(SCREEN_TYPE){}void Enable(int type){_type type;}void FlushLogToScreen(const logmessage lg)//显示器显示{printf([%s][%d][%s][%d][%s] %s,lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());}void FlushLogToFile(const logmessage lg)//文件显示{std::ofstream out(_logfile, std::ios::app);//文件流且追加细节if (!out.is_open())return;char logtxt[2048];snprintf(logtxt, sizeof(logtxt), [%s][%d][%s][%d][%s] %s,lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());out.write(logtxt, strlen(logtxt));//写入out.close();}void FlushLog(const logmessage lg){// 加过滤逻辑 --- TODO筛选等级宏参/条件编译等处理LockGuard lockguard(glock);//加锁兼容多线程switch (_type)//类型选择{case SCREEN_TYPE:FlushLogToScreen(lg);break;case FILE_TYPE:FlushLogToFile(lg);break;}}void logMessage(std::string filename, int filenumber, int level, const char* format, ...){logmessage lg;lg._level LevelToString(level);//日志等级lg._id getpid();//pidlg._filename filename;//文件名lg._filenumber filenumber;//行号lg._curr_time GetCurrTime();//时间va_list ap;//可变参数处理va_start(ap, format);//可变参数初始化char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, ap);//可变参数格式化转为字符串放入指定log_info中va_end(ap);//销毁lg._message_info log_info;// 打印出来日志FlushLog(lg);}~Log(){}private:int _type;//打印目标的类型1.屏幕 2.文件std::string _logfile;};Log lg;//优化日志输出格式VA_ARGS()/__VA_ARGS__属于C99语法作用是让宏支持可变参数//并在此之前加两个##表示兼容没有可变参数的情况。自动去掉前面的逗号。匹配只需要的参数即可
#define LOG(Level, Format, ...) \do \{ \lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \} while (0)
#define EnableScreen() \do \{ \lg.Enable(SCREEN_TYPE); \} while (0)
#define EnableFILE() \do \{ \lg.Enable(FILE_TYPE); \} while (0)
};7.6、日志的维护与优化 由于日志文件可能会不断增长并占用大量磁盘空间因此需要对它们进行定期的维护和优化。 常用的方法包括 1.使用logrotate工具定期压缩、归档和删除旧的日志文件。 2.配置日志文件的轮转策略如按天、周或月进行轮转。 3.设置日志文件的大小限制当文件达到指定大小时自动进行轮转。 4.清理不必要的日志文件以释放磁盘空间。