【Linux】信号量,线程池

news/2024/10/30 13:48:32/

目录

信号量

初始化​编辑

销毁

 等待

发布

基于环形队列的生产消费模型

问题解答:

代码:

线程池

线程池的实现

(1)初始化,构造大致框架

(2)创建线程

(3)创建任务

(4)完善线程要执行的任务

(5)更新初始化Init()函数

代码:

日志


信号量

POSIX 信号量和 SystemV 信号量作用相同, 都是用于同步操作, 达到无冲突的访问共
享资源目的。 但 POSIX 可以用于线程间同步。

信号量本质就是一个计数器,对公共资源的预定机制;


初始化

sem_t _data_sem;
sem_init(&_data_sem,0,0);
//pshared:0 表示线程间共享, 非零表示进程间共享
//value: 信号量初始值

销毁

sem_destroy(&_data_sem);

 等待

功能: 等待信号量, 会将信号量的值减 1;

    void P(sem_t &s)//申请信号量,--{sem_wait(&s);}

发布

功能: 发布信号量, 表示资源使用完毕, 可以归还资源了。 将信号量值加 1。

    void V(sem_t &s)//释放资源,++{sem_post(&s);}

基于环形队列的生产消费模型

之前我们写的生产消费模型是基于 queue 的,其空间可以动态分配,现在基于固定大小
的环形队列重写这个程序(POSIX 信号量) ;

环形队列采用数组模拟, 用模运算来模拟环状特性;

(1)初始化

(2)完善生产消费代码

(3)完善main

当然任务不止是参数,也可以是类:

问题解答:

(1)上面的是单生产单消费的例子,那多生产多消费呢?

两个锁:多个生产者竞争一个锁,多个消费者竞争一个锁;其实本质还是单生产单消费;但是由于处理数据和构造数据都需要时间,所以多生产多消费效率更改高;

(2)在多生产多消费时,是先加锁还是先申请信号量?

先申请信号量;这个问题就好比你去电影院看电影,是先排队(此时你并没有买票)还是先买票的问题,肯定是先买票效率更高,要不然排到你,你没有电影票还是进不去;

(3)为什么信号量对资源进行使用、申请时,不判断一下条件是否满足?

因为信号量本身就是一个判断条件;

代码:
#pragma once#include<iostream>
#include<pthread.h>
#include<semaphore.h>
#include<vector>
#include<string>
#include<unistd.h>
#include<sys/types.h>using namespace std;
const int  defaultcp =5;
template<typename T>
class Ringqueue
{
private:void P(sem_t &s)//申请信号量,--{sem_wait(&s);}void V(sem_t &s)//释放资源,++{sem_post(&s);}
public:Ringqueue(int max_cp = defaultcp):_max_cp(max_cp),_ringqueue(max_cp),_c_step(0),_p_step(0){sem_init(&_data_sem,0,0);sem_init(&_space_sem,0,max_cp);}~Ringqueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);}void Push(const T &in)//生产{P(_space_sem);_ringqueue[_p_step]=in;_p_step++;_p_step%=_max_cp;V(_data_sem);}void Pop(T *out)//消费{P(_data_sem);*out=_ringqueue[_c_step];_c_step++;_c_step%=_max_cp;V(_space_sem);}private:vector<T>_ringqueue;int _p_step;int _c_step;int _max_cp;sem_t _data_sem;sem_t _space_sem;
};

线程池

线程池其实就是一种线程的使用模式;

线程过多会带来调度开销, 进而影响缓存局部性和整体性能。 而线程池维护着多个线程, 等待着监督管理者分配可并发执行的任务。 这避免了在处理短时间任务时创建与销毁线程的代价。 线程池不仅能够保证内核的充分利用, 还能防止过分调度。

线程池的实现

(1)初始化,构造大致框架

大致框架要有多个线程(用vector维护),要有任务队列(task_queue)能生产任务;

(2)创建线程

完成线程的创建,这里我直接用的上一篇文章自己封装的线程;

(3)创建任务

(4)完善线程要执行的任务

(5)更新初始化Init()函数

这样就完成了线程池的主要内容了,剩下的就是修改一下细节部分即可;

线程池的实现:构成出大致框架,在任务的函数中,注意如果任务列表中没有任务,那么线程就要处于等待状态,如果创建出一个任务后,就可以唤醒一个线程去执行即可;

代码:
#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include "Thread.hpp"using namespace std;
const int defaultnum = 5;
template <typename T>
class ThreadPool
{
private:void WakeUpAll(){pthread_cond_broadcast(&_cond);}void Lock(){pthread_mutex_lock(&_mutex);}void Unlock(){pthread_mutex_unlock(&_mutex);}void WakeUp(){pthread_cond_signal(&_cond);}bool isEmpty(){return _task_queue.empty();}void HandlerTask(const string &name){while (true){// 取任务Lock();while (isEmpty() && _isrunning){// 休眠_sleep_num++;pthread_cond_wait(&_cond, &_mutex);_sleep_num--;}if (isEmpty() && !_isrunning){cout << name << "quit..." << endl;Unlock();break;}// 有任务T t = _task_queue.front();_task_queue.pop();Unlock();// 处理任务t();cout << name << t.Excute() << "任务处理完" << endl;}}public:ThreadPool(int thread_num = defaultnum) : _thread_num(thread_num), _isrunning(false), _sleep_num(0){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Init(){func_t func = bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);// 创建线程for (int i = 0; i < _thread_num; i++){string name = "thread-" + to_string(i + 1);_threads.emplace_back(name, func);}}void Start(){_isrunning = true;for (auto &thread : _threads){thread.start();}}void Stop(){Lock();_isrunning = false;WakeUpAll();Unlock();}void Equeue(const T &in){Lock();if (_isrunning){// 生产任务_task_queue.push(in);// 唤醒线程if (_sleep_num > 0){WakeUp();}}Unlock();}private:int _thread_num;vector<Thread> _threads; // 线程queue<T> _task_queue;    // 任务,共享资源bool _isrunning;int _sleep_num; // 休眠的个数pthread_mutex_t _mutex;pthread_cond_t _cond;
};

日志

日志:软件运行的记录信息、向显示器打印、向文件打印、特定的格式;

【日志等级】【pid】【filename】【filenumber】【time】日志内容(支持可变参数)

日志等级:DEBUG、INFO、WARNING、ERROR、FATAL(致命的);

在初始化的时候,主要就是可变参数的初始化

其实写到上面这一步就以及完成的日志的实现;

我们来运行一下代码来看看:

main函数:

    cout<<gettime()<<endl;Log lg;lg.logMessage("main.cc",10,DEBUG,"hello %d,world%c,hello %f\n",10,'A',3.14);

 

下面我们只需要完善一下使该日志信息可以向显示器中打印,也可以向文件中打印;

设置一个类型_type;默认是向显示器打印;在执行打印代码时,只需要判断一下_type即可;

如果是向显示器打印,直接printf即可;如果是文件中打印,需要先打开对应的文件,在将日志信息写入;

完整代码:

#pragma once#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include <stdarg.h>
// #include<stdio.h>
#include<ostream>
#include <fstream>using namespace std;#define SCREEN_TYPE 1
#define FILE_TYPE 2
const string glogfile ="./log.txt";// 日志等级
enum
{DEBUG = 1,INFO,WARNING,ERROR,FATAL
};string levelTo_string(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";}
}string gettime()
{time_t now = time(nullptr); // now就是时间戳struct tm *curr_time = localtime(&now);char buff[128];snprintf(buff, sizeof(buff), "%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 buff;
}class Logmessage
{
public:string _level;pid_t _pid;string _filename;int _filenumber;string _curr_time;string _message_info;
};class Log
{
private:void FlushLogScreen(Logmessage &lg){printf("[%s][%d][%s][%d] %s",lg._level.c_str(),lg._pid,lg._filename.c_str(),lg._filenumber,lg._message_info.c_str());}void FlushLogFile(Logmessage &lg){ofstream out(_logfile.c_str());if(!out.is_open())return;char buff[2048];snprintf(buff,sizeof(buff),"[%s][%d][%s][%d] %s",lg._level.c_str(),lg._pid,lg._filename.c_str(),lg._filenumber,lg._message_info.c_str());out.write(buff,strlen(buff));out.close();}public:Log(const string &logfile = glogfile) : _type(SCREEN_TYPE), _logfile(logfile){}~Log(){}void Enable(int type){_type = type;}void FlushLog(Logmessage &lg){switch (_type){case SCREEN_TYPE:FlushLogScreen(lg);break;case FILE_TYPE:FlushLogFile(lg);break;}}void logMessage(string filename, int filenumber, int level, const char *format, ...){Logmessage lg;lg._level = levelTo_string(level);lg._pid = getpid();lg._filename = filename;lg._filenumber = filenumber;lg._curr_time = gettime();va_list ap;va_start(ap, format);char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, ap);va_end(ap);lg._message_info = log_info;FlushLog(lg);}private:int _type;string _logfile;
};

http://www.ppmy.cn/news/1543116.html

相关文章

单细胞数据分析(三):单细胞聚类分析

文章目录 介绍目的加载R包数据链接导入数据可视化聚类选择聚类分辨率值输出结果系统信息介绍 单细胞聚类分析是一种用于解析单细胞RNA测序(scRNA-seq)数据的方法,旨在将成千上万个单细胞根据它们的基因表达模式分组到不同的类别或“簇”中。每个簇代表了具有相似基因表达特…

rnn/lstm

tip&#xff1a;本人比较小白&#xff0c;看到july大佬的文章受益匪浅&#xff0c;现在其文章基础上加上自己的归纳、理解&#xff0c;以及gpt的答疑&#xff0c;如果有侵权会删。 july大佬文章来源&#xff1a;如何从RNN起步&#xff0c;一步一步通俗理解LSTM_rnn lstm-CSDN博…

微服务的发布策略与设计约束

分布策略微服务架构中的蓝绿发布和金丝雀发布策略是两种常见的版本控制和发布管理方法,旨在提高软件的发布安全性和可用性。 蓝绿发布 概念:蓝绿发布是一种将两个相同的环境(蓝和绿)进行交替使用的发布策略。在某个时刻,只有一个环境在处理用户请求,而另一个环境则处于…

EJB项目如何升级SpringCloud

记录某金融机构老项目重构升级为微服务过程1 如何从EJB架构拆分微服务 这个非常有趣的过程&#xff0c;整个过程耗时大致接近半年时光&#xff0c;需要考虑到重构升级保留原来的业务线&#xff0c;而且还要考虑后续的维护成本&#xff0c;保留现有的数据库表结构&#xff0c;…

vue3父组件控制子组件表单验证及获取子组件数值方法

1、关键部分的代码如下&#xff0c;我努力交代清楚了&#xff0c;希望能让大家看懂。 <template><KeepAlive><component ref"comp" :is"compNames[steps[compIndex].comp]" /></KeepAlive><el-button click"prevBtn"…

流媒体协议.之(RTP,RTCP,RTSP,RTMP,HTTP)(三)

本文&#xff0c;分析&#xff0c;贴出一些博主的关键源码 git地址&#xff1a;https://github.com/ireader/media-server?tabreadme-ov-file 网络上也有开源的库&#xff0c;可以用这些。 推流协议库有&#xff0c;librtmp&#xff0c;librtp&#xff0c;jrtplib等&#xf…

Logo内容移除、盒子阴影、精灵图、滑动门

Logo内容移除 像小米&#xff0c;百度&#xff0c;京东&#xff0c;淘宝都有自己的Logo(网页的标志), 1.将a标签的背景background作为logo使用&#xff0c;所以点击图片也可以跳转。 2. 可以在logo中设置文字&#xff0c;但是要将文字移除给用户更好的体验。 3. &#xff08;…

矩阵压缩格式转换:COO转换CSC(C++)

目录 一、基本理论 1.1 COO格式 1.2 CSR格式 1.3 CSC格式 二、代码实现 三、测试 一、基本理论 稀疏矩阵&#xff08;Sparse Matrix&#xff09;是大部分元素为零的矩阵&#xff0c;与之相对应的是稠密矩阵&#xff08;Dense Matrix&#xff09;。科学领域、工程计算、图…