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
    };
    // 将日志等级转化成string
    std::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()<<"  "<<11
            template <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()
}

我们下期见。

相关推荐
一勺-_-4 分钟前
全栈:如何判断自己应该下载哪个版本的Tomcat
java·tomcat
success8 分钟前
【爆刷力扣-二叉树】层次遍历
算法
ONE_Gua10 分钟前
魔改chromium源码——解除 iframe 的同源策略
前端·后端·浏览器
现在没有牛仔了11 分钟前
举例说明什么是Redis缓存击穿,以及如何解决。
java·redis·后端
用户15129054522011 分钟前
mysql8的collate问题和修改
前端
青云交13 分钟前
Java 大视界 -- 基于 Java 的大数据分布式计算在气象灾害数值模拟与预警中的应用(388)
java·大数据·flink·分布式计算·预警系统·数值模拟·气象灾害
用户15129054522019 分钟前
MySQL的sql_mode模式说明及设置
前端·后端
CHEN5_0228 分钟前
Java基础知识总结
java·开发语言
Jackson__28 分钟前
MCP Server 实战:构建自己的扩展服务
前端·mcp
阿 柒29 分钟前
网络基础——网络层级
运维·服务器·网络