linux信号量和日志
目录
封装条件变量
POSIX信号量
初始化信号量
销毁信号量
等待信号量
发布信号量
封装信号量
基于环形队列的⽣产消费模型
日志
封装
封装条件变量
比较简单,不做讲解。
namespace CondModule
{class Cond{public:Cond(){pthread_cond_init(&_cond,nullptr);}void Wait(Mutex& mutex){pthread_cond_wait(&_cond,mutex.Get());}void Singal(){pthread_cond_signal(&_cond);}void Broadcast(){pthread_cond_broadcast(&_cond);}~Cond(){pthread_cond_destroy(&_cond);}private:pthread_cond_t _cond;};
}
至此,我们封装了互斥锁,线程和条件变量,所以我们可以用我们自己的轮子来写前面的阻塞队列。
using namespace MutexModule;
using namespace CondModule;const int defaultcap = 5;
template <typename T>
class BlockQueue
{bool IsFull() { return _q.size() >= _cal; }bool IsEmpty() { return _q.empty(); }public:BlockQueue(int cal = defaultcap): _cal(cal),_psleep_num(0),_csleep_num(0){}void Enqueue(const T &in){// pthread_mutex_lock(&_mutex); //上锁LockGuard lockguard(_mutex);//上锁// 生产者调用while (IsFull()){_psleep_num++;// pthread_cond_wait(&_full_cond, &_mutex);//等待_full_cond.Wait(_mutex);//等待_psleep_num--;}_q.push(in);if (_csleep_num > 0)// pthread_cond_signal(&_empty_cond);//唤醒_empty_cond.Singal();}T Pop(){// 消费者调用// pthread_mutex_lock(&_mutex);//上锁LockGuard lockguard(_mutex);//上锁while (IsEmpty()){_csleep_num++;// pthread_cond_wait(&_empty_cond, &_mutex);//等待_empty_cond.Wait(_mutex);_csleep_num--;}T data = _q.front();_q.pop();if (_psleep_num > 0)// pthread_cond_signal(&_full_cond);//唤醒_full_cond.Singal();//唤醒return data;}~BlockQueue(){}
private:std::queue<T> _q;int _cal; // 大小Mutex _mutex;Cond _full_cond;Cond _empty_cond;int _csleep_num; // 消费者休眠的个数int _psleep_num; // 生产者休眠的个数
};
POSIX信号量
POSIX信号量和SystemV信号量作⽤相同,都是⽤于同步操作,达到⽆冲突的访问共享资源⽬的。但 POSIX可以⽤于线程间同步。
初始化信号量
销毁信号量
等待信号量
发布信号量
封装信号量
namespace SemModule
{const int defaultvaule=1;class Sem{public:Sem(unsigned int value=defaultvaule){sem_init(&_sem,0,value);}void P(){sem_wait(&_sem);//-- 申请信号量 原子的}void V(){sem_post(&_sem);//++ 释放信号量 原子的}~Sem(){sem_destroy(&_sem);}private:sem_t _sem;};
}
基于环形队列的⽣产消费模型
- 形队列采⽤数组模拟,⽤模运算来模拟环状特性
- 环形结构起始状态和结束状态都是⼀样的,不好判断为空或者为满,所以可以通过加计数器或者 标记位来判断满或者空。另外也可以预留⼀个空的位置,作为满的状态
理解:
1.当队列为空,生产者先运行。
2.当队列为满,消费者先运行。
3.消费者不得超过生产者。
4.当生产者和消费者在同一个位置时,只能为空或为满,也就是说只要不为空和满,消费者和生产者就能同时进行(并发)。
我们先看单生产单消费:
代码:
const int defaultcap = 5;
using namespace SemModule;
template <class T>
class RingQueue
{
public:RingQueue(int cap = defaultcap): _cap(cap), _rq(cap), _blank_sem(cap), _data_sem(0),_p_step(0), _c_step(0){}void Equeue(const T &in){// 生产者调用_blank_sem.P(); // 申请空位置信号量_rq[_p_step++] = in; // 生产_p_step %= _cap; // 维持环状_data_sem.V(); // 数据信号量要++}void Pop(T *out) // 输出型参数{// 消费者调用_data_sem.P(); // 申请数据信号量*out = _rq[_c_step++]; // 将数据带出去消费_c_step %= _cap; // 维持环状_blank_sem.V(); // 空位置信号量要++}
private:std::vector<T> _rq;int _cap; // 大小// 生产者Sem _blank_sem; // 空位置信号量int _p_step;// 消费者Sem _data_sem; // 数据信号量int _c_step;
};
我们来看多生产,多消费:
多生产多消费,我们需要用锁。
不管是生产还是消费,任意时刻都是单个线程生产或消费,所以我们要用锁。
1.
问题:生产者或消费者是先申请信号量还是先加锁?
先申请信号量再加锁,这样快!
因为:我们可以先将资源全部申请出去,然后一个一个生产或消费。
如果是先加锁再申请信号量,这样就会将资源一个一个申请然后消费!显然,第一种快!
2.
我们写好了单生产单消费,就已经维持了消费者和生产者的互斥和同步,要写多生产多消费其实就是要维持消费者和消费者之间,生产者和生产者之间的同步与互斥关系!需要俩把锁。
3.
多线程使用资源,有俩种场景:
将资源整体使用,应用mutex+2元信号量(也就是条件变量),将资源按照不同块,分批使用,应用信号量。
代码:
const int defaultcap = 5;
using namespace SemModule;
using namespace MutexModule;
template <class T>
class RingQueue
{
public:RingQueue(int cap = defaultcap): _cap(cap), _rq(cap), _blank_sem(cap), _data_sem(0),_p_step(0), _c_step(0){}void Equeue(const T &in){// 生产者调用_blank_sem.P(); // 申请空位置信号量{LockGuard lockguard(_pmutex);_rq[_p_step++] = in; // 生产_p_step %= _cap; // 维持环状}_data_sem.V(); // 数据信号量要++}void Pop(T *out) // 输出型参数{// 消费者调用_data_sem.P(); // 申请数据信号量{LockGuard lockguard(_cmutex);*out = _rq[_c_step++]; // 将数据带出去消费_c_step %= _cap; // 维持环状}_blank_sem.V(); // 空位置信号量要++}private:std::vector<T> _rq;int _cap; // 大小Mutex _cmutex;//消费者锁Mutex _pmutex;//生产者锁// 生产者Sem _blank_sem; // 空位置信号量int _p_step;// 消费者Sem _data_sem; // 数据信号量int _c_step;
};
日志
⽇志认识
计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信 息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯ 具。
⽇志格式以下⼏个指标是必须得有的:
- 时间戳
- ⽇志等级
- ⽇志内容
以下⼏个指标是可选的:
- ⽂件名⾏号
- 进程,线程相关id信息等
⽇志有现成的解决⽅案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采⽤⾃定义⽇志的 ⽅式。
我们想要的⽇志格式如下:
封装
刷新策略
日志:
我们采用内部类方法实现:
外部类可以直接通过内部类的对象访问其成员(因为内部类的定义在外部类作用域内可见)。
内部类使用外部类成员需要对象或者指针。
最后我们可以使用宏来简化调用:
代码:
namespace LogModule
{using namespace MutexModule;const std::string gsep = "\r\n";// 刷新策略基类class LogStrategy{public:~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 显示器打印:子类class ConsoleLogStrategy : public LogStrategy{public:void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::cout << message << gsep;}private:Mutex _mutex;};const std::string defaultpath = "./log";const std::string defaultfile = "my.log";// 文件打印:子类class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultpath,const std::string &file = defaultfile): _path(path), _file(file){LockGuard lockguard(_mutex);if (std::filesystem::exists(_path))return;try{std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;std::ofstream out(filename, std::ios::app); // 追加方式写入if (!out.is_open())return;out << message << gsep;out.close();}private:std::string _path;std::string _file;Mutex _mutex;};// 日志等级enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 将日志等级转化成stringstd::string LevelToStr(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";default:return "UNKNOWN";}}// 获得当前时间std::string GetTimeStamp(){time_t curr = time(nullptr);struct tm curr_tm;localtime_r(&curr, &curr_tm);char timebuffer[128];snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",curr_tm.tm_year + 1900,curr_tm.tm_mon + 1,curr_tm.tm_mday,curr_tm.tm_hour,curr_tm.tm_min,curr_tm.tm_sec);return timebuffer;}//日志类class Logger{public:Logger(){EnableConsoleStrategy();}void EnableConsoleStrategy(){_fflush_strategy = std::make_unique<ConsoleLogStrategy>();}void EnableFileStrategy(){_fflush_strategy = std::make_unique<FileLogStrategy>();}// 将来是一条日志class LogMessage{public:LogMessage(LogLevel &level, std::string &src_name,int line_number, Logger &logger): _curr_time(GetTimeStamp()), _level(level),_src_name(src_name), _line_number(line_number),_logger(logger), _pid(getpid()){std::stringstream ss;ss << "[" << _curr_time << "]"<< "[" << LevelToStr(_level) << "]"<< "[" << _pid << "]"<< "[" << _src_name << "]"<< "[" << _line_number << "]"<< "- ";_loginfo = ss.str();}// LogMessage()<<" "<<11template <class T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._fflush_strategy){_logger._fflush_strategy->SyncLog(_loginfo);}}private:std::string _curr_time;LogLevel _level;pid_t _pid;std::string _src_name;int _line_number;std::string _loginfo; // 一条日志合并的完整信息Logger &_logger;};// 重载()LogMessage operator()(LogLevel level, std::string name, int line){return LogMessage(level, name, line, *this);}private:std::unique_ptr<LogStrategy> _fflush_strategy;};// 全局Logger logger;
// 使用宏,简化用户操作,获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
我们下期见。