目录
封装条件变量
比较简单,不做讲解。
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()
}
我们下期见。