目录
POSIX信号量
POSIX信号量和SystemV信号量作⽤相同,都是⽤于同步操作,达到⽆冲突的访问共享资源⽬的。但POSIX可以⽤于线程间同步。
信号量接口
#include <semaphore.h>
初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
销毁信号量
int sem_destroy(sem_t *sem);
等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
信号量实现的一些细节
上⼀节⽣产者-消费者的例⼦(Linux系统编程:线程互斥与同步)是基于queue的,其空间可以动态分配,现在基于固定⼤⼩的环形队列重写这个程序(POSIX信号量)。
接下来,我们来了解一些实现的细节。


队列的容量是有限的,刚开始时,队列为空,一定是生产者先运行。此时生产者和消费者访问同一个位置,生产者还没生产数据,消费者就开始消费数据,这是不行的,所以,必须等到生产者生产数据之后,消费者才可以消费数据。
当队列为满时,必须让消费者先运行。此时生产者,消费者又指向了同一个位置,当消费者拿取数据时,生产者是不能立即生产数据的,要不然消费者还没有获取到数据,生产者已经把数据覆盖了,会导致数据错乱。
依照以上两种情况,生产者和消费者之间需要维护互斥与同步的关系。
当队列不为空,不为满时,生产者和消费者肯定不是指向同一个位置的,所以,生产者和消费者就可以并发执行了。
信号量实现
Sem.hpp
#pragma once
#include<iostream>
#include<semaphore.h>
class Sem
{
public:
Sem(int num):_initnum(num)
{
sem_init(&_sem,0,_initnum);
}
void P()
{
int n=sem_wait(&_sem);
(void)n;
}
void V()
{
int n=sem_post(&_sem);
(void)n;
}
~Sem()
{
sem_destroy(&_sem);
}
private:
sem_t _sem;
int _initnum;
};
RingQueue.hpp
#pragma once
#include<iostream>
#include<vector>
#include"Sem.hpp"
static int gcap=5; //for debug
template <typename T>
class RingQueue
{
public:
RingQueue(int cap=gcap):_cap(cap),_ring_queue(cap),_space_sem(cap),_data_sem(0),_p_step(0),_c_step(0)
{}
//消费数据
void Pop(T*out)
{
//数据申请信号量,消费者拿到数据
_data_sem.P();
*out=_ring_queue[_c_step++];
_c_step%=_cap;
//空间释放信号量,让生产者申请空间信号量拿到空间
_space_sem.V();
}
//生产数据
void Enqueue(const T&in)
{
//空间申请信号量
_space_sem.P();
//生产数据
_ring_queue[_p_step++]=in;
//维持环形特点
_p_step%=_cap;
//数据释放信号量,让消费者申请数据信号量拿到数据
_data_sem.V();
}
~RingQueue()
{}
private:
std::vector<T> _ring_queue; //临界资源
int _cap;
Sem _space_sem; //空间信号量
Sem _data_sem; //数据信号量
//生产和消费的位置
int _p_step;
int _c_step;
};
main.cc
#include"RingQueue.hpp"
#include<pthread.h>
#include<unistd.h>
void*consumer(void*args)
{
RingQueue<int> *rq=static_cast<RingQueue<int>*>(args);
while(1)
{
sleep(1);
int data=0;
rq->Pop(&data);
std::cout<<"消费了一个数据:"<<data<<std::endl;
}
}
void*producer(void*args)
{
RingQueue<int> *rq=static_cast<RingQueue<int>*>(args);
int data=1;
while(1)
{
rq->Enqueue(data);
std::cout<<"生产了一个数据:"<<data<<std::endl;
data++;
}
}
int main()
{
RingQueue<int> *rq=new RingQueue<int>();
pthread_t c,p;
pthread_create(&c,nullptr,consumer,(void*)rq);
pthread_create(&c,nullptr,producer,(void*)rq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
delete rq;
return 0;
}
makefile
ringqueue:main.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f ringqueue
思考延伸
问题1:我们在申请信号量的过程中没有用到锁,难道不会不安全吗?
刚开始时,队列为空,生产者先申请空间信号量,生产数据,然后在数据信号量V操作,唤醒消费者申请数据信号量,消费者才能消费数据。这个过程本身就已经完成了生产者和消费者之间的互斥与同步关系。
当队列为满时,生产者申请空间信号量失败,就被阻塞住,此时消费者申请数据信号量,消费数据,然后再唤醒生产者申请空间信号量,生产者才能生产数据,所以这个过程本身也完成了生产者与消费者之间的互斥与同步关系。
而队列不为空也不为满时,生产者和消费者可以并发执行。
问题2:我们怎么没有像线程互斥同步,在临界区内部判断资源是否就绪?
信号量本质就是一把计数器,是对于资源的一种预定机制。
对信号量进行P操作的时候,计数器--,虽然是申请信号量,但本质就是对资源是否就绪进行判断。有多少资源就可以预定多少资源,计数器减到0就不能预定了,绝不会预定出的资源比实际资源多,也就是说有多少资源就可以有多少个生产者线程。
重新理解锁:
demo中我们将信号量大小设置为5,如果大小为1,也就是所谓的二元信号量。一个线程申请信号量之后就不可能再有第二个线程成功申请信号量,因为信号量已经减为0了,就形成了一把锁,控制线程的开关。
怪不得觉得申请信号量和释放信号量和加锁上锁操作如此相似,原来锁就是资源只有一份的信号量,线程P操作申请信号量,信号量减为0,相当于给当前线程上锁,线程V操作,信号量++,就相当于当前线程释放锁,然后其他线程可以去竞争申请锁。
所以,锁是信号量的一种特殊情况。

日志与策略模式
日志认识
计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信
息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯
具。
⽇志格式以下⼏个指标是必须得有的
• 时间戳 • ⽇志等级 • ⽇志内容
以下⼏个指标是可选的
• ⽂件名⾏号 • 进程,线程相关id信息等
//获取时间戳
//tloc设置为nullptr
time_t time(time_t* tloc);
//timep获取到的时间戳
//result输出型参数
struct tm* localtime_r(const time_t* timep, struct tm* result);
struct tm
{
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
这里设计的日志格式如下

logger.hpp
#pragma once
#include <iostream>
#include <filesystem>
#include <fstream>
#include <string>
#include <sstream>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"
enum class LoggerLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LoggerLevelToString(LoggerLevel level)
{
switch (level)
{
case LoggerLevel::DEBUG:
return "Debug";
case LoggerLevel::INFO:
return "Info";
case LoggerLevel::WARNING:
return "Warning";
case LoggerLevel::ERROR:
return "Error";
case LoggerLevel::FATAL:
return "Fatal";
default:
return "Unknown";
}
}
std::string GetCurrentTime()
{
// 获取时间戳
time_t timep = time(nullptr);
// 把时间戳转化为时间格式
struct tm currtm;
localtime_r(&timep, &currtm);
// 转化为字符串
char buffer[64];
snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d-%02d-%02d",
currtm.tm_year + 1900, currtm.tm_mon + 1, currtm.tm_mday,
currtm.tm_hour, currtm.tm_min, currtm.tm_sec);
return buffer;
}
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &logmessage) = 0;
};
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
~ConsoleLogStrategy()
{
}
virtual void SyncLog(const std::string &logmessage) override
{
{
LockGuard lockguard(&_lock);
std::cout << logmessage << std::endl;
}
}
private:
Mutex _lock;
};
const std::string default_dir_path_name = "log";
const std::string default_filename = "test.log";
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string dir_path_name = default_dir_path_name,
const std::string filename = default_filename)
: _dir_path_name(dir_path_name), _filename(filename)
{
if (std::filesystem::exists(_dir_path_name))
{
return;
}
try
{
std::filesystem::create_directories(_dir_path_name);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\r\n";
}
}
~FileLogStrategy()
{
}
virtual void SyncLog(const std::string &logmessage) override
{
{
LockGuard lock(&_lock);
std::string target = _dir_path_name;
target += '/';
target += _filename;
std::ofstream out(target.c_str(), std::ios::app);
if (!out.is_open())
{
return;
}
out << logmessage << "\n";
out.close();
}
}
private:
std::string _dir_path_name;
std::string _filename;
Mutex _lock;
};
class Logger
{
public:
Logger()
{
}
void EnableConsoleStrategy()
{
_strategy = std::make_unique<ConsoleLogStrategy>();
}
void EnableFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
class LogMessage
{
public:
LogMessage(LoggerLevel level, std::string filename, int line, Logger& logger)
: _curr_time(GetCurrentTime()), _level(level), _pid(getpid())
, _filename(filename), _line(line), _logger(logger)
{
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << LoggerLevelToString(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "]"
<< " - ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 时间戳
LoggerLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename; // 文件名
int _line; // 行号
std::string _loginfo; // 一条合并完成的,完整的日志信息
Logger &_logger; // 提供刷新策略的具体做法
};
LogMessage operator()(LoggerLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _strategy;
};
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define EnableConsoleStrategy() logger.EnableConsoleStrategy()
#define EnableFileStrategy() logger.EnableFileStrategy()
Mutex.hpp
#pragma once
#include<iostream>
#include<mutex>
#include<pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex* _mutex)
:_mutexp(_mutex)
{
_mutexp->Lock();
}
~LockGuard()
{
_mutexp->Unlock();
}
private:
Mutex* _mutexp;
};
main.cc
#include"Logger.hpp"
int main()
{
EnableConsoleStrategy();
LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;
LOG(LoggerLevel::WARNING) << "hello linux" << ", 6.66 " << 123;
LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;
LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;
LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;
return 0;
}
我们在外部类 Logger 里重载了运算符(),返回了一个 LogMessage 类的临时对象。
LOG(level)之后会进入logger的重载()的函数,返回一个 LogMessage 类的临时对象。

所以相当于这前面部分是LogMessage类的临时对象,内部类LogMessage里重载了运算符<<,返回临时对象的引用,保证了后续所有的<<都会进入这个函数进行解析

临时对象具有常性,它的生命周期随着一条语句执行完后结束,于是调用LogMessage析构函数,
依照策略将日志信息输出

此篇完。
2613

被折叠的 条评论
为什么被折叠?



