并發編程的守護者:信號量與日志策略模式解析

一、信號量

關于信號量的介紹在深入Linux內核:IPC資源管理揭秘
這篇文章當中已經做了初步的介紹了,相信大家對于信號量已經有了初步的認知了。

今天,我們就來探討如何實現信號量。

1. 信號量的接口

//初始化信號量
//成功了,返回0,失敗了,返回-1并且設置錯誤碼
//sem初始化的信號量
//pshared設置為0,代表線程間使用
//value信號量的初始值
int sem_init(sem_t* sem, int pshared, unsigned int value);
//銷毀信號量
//成功返回0,失敗返回-1并且設置錯誤碼
int sem_destroy(sem_t* sem);
//減少信號量
//成功返回0,失敗返回-1并且設置錯誤碼
int sem_wait(sem_t* sem);
//增加信號量
//成功返回0,失敗返回-1并且設置錯誤碼
int sem_post(sem_t* sem);

2. 信號量實現的一些細節問題

信號量的接口就了解到這里。我們實現的信號量是基于一個環形隊列實現的(數組)

接下來,我們了解實現的一些細節。

在這里插入圖片描述

隊列的容量是有限的,剛開始時,隊列為空,一定是生產者先運行。此時生產者和消費者訪問同一個位置,生產者還沒生產數據,消費者就開始消費數據,這是不行的,所以,必須等到生產者生產數據之后,消費者才可以消費數據。所以,生產者和消費者之間需要維護互斥與同步的關系

在這里插入圖片描述

當隊列為滿時,必須讓消費者先運行。此時生產者,消費者又指向了同一個位置,當消費者拿取數據時,生產者是不能立即生產數據的,要不然消費者還沒有獲取到數據,生產者已經把數據覆蓋了,不就導致數據錯亂了嗎!所以,這個過程不僅需要生產者和消費者互斥的獲取數據,還需要同步

當隊列不為空,不為滿時,生產者和消費者肯定不是指向同一個位置的,所以,生產者和消費者不就可以并發執行了

3. 信號量的實現

Sem.hpp

#pragma once
#include<iostream>
#include<vector>
#include<unistd.h>
#include<semaphore.h>class Sem
{
public:Sem(int num):_initnum(num){sem_init(&_sem, 0, _initnum);}void P(){int n = sem_wait(&_sem);}void V(){int n = sem_post(&_sem);}~Sem(){sem_destroy(&_sem);}
private:sem_t _sem;int _initnum;
};

RingQueue.hpp

#include"Sem.hpp"int gcap = 5;
template<typename T>
class RingQueue
{
public:RingQueue(int cap = gcap):_ring_queue(cap),_cap(cap),_space_sem(cap),_data_sem(0),_c_step(0),_p_step(0){}void EnQueue(const T& in){//先申請空間信號量,對資源的一種預定機制_space_sem.P();//生產數據_ring_queue[_p_step++] = in;_p_step %= _cap;_data_sem.V();}void Pop(T* out){//先申請數據信號量_data_sem.P();//消費數據*out = _ring_queue[_c_step++];_c_step %= _cap;_space_sem.V();}~RingQueue(){}
private:std::vector<T> _ring_queue;int _cap;Sem _space_sem;Sem _data_sem;int _c_step;int _p_step;
};

main.cc

#include"RingQueue.hpp"void* consumer(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true){int data = 0;rq->Pop(&data);std::cout << "消費者消費了一個數據" << data << std::endl;}
}void* productor(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int data = 1;while(true){sleep(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(&p, nullptr, productor, (void*)rq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}

Makefile

ringqueue:main.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f ringqueue

4. 信號量實現過程中的一些疑問

這是一個基于單生產者,單消費者的信號量

問題1:我們在申請信號量的過程當中并沒有用到鎖,難道就不怕數據不安全嗎

剛開始時,隊列為空,生產者先申請信號量,生產數據,然后在V操作,喚醒消費者,消費者才能消費數據。這個過程本身就已經完成了生產者和消費者之間的互斥與同步關系

當隊列為滿時,生產者申請信號量失敗,就被阻塞住,此時消費者申請信號量,消費數據,然后再喚醒生產者,生產者才能生產數據,所以這個過程本身也完成了生產者與消費者之間的互斥與同步關系

而隊列不為空也不為滿時,生產者和消費者可以并發執行

問題2:我們怎么沒有在臨界區內部,判斷資源是否就緒呢

信號量本身就是一把計數器,是對于資源的一種預定機制,對信號量進行P操作的時候,雖然是申請信號量,但本質就是對資源是否就緒進行判斷。有多少資源就可以預定多少資源,絕不會預定出的資源比實際資源多,也就是說有多少資源就可以有多少個生產者線程

重新理解信號量。

我們把信號量設置為5,如果信號量設置為1呢?不就是二元信號量一個線程申請信號量之后就不可能再有第二個線程成功申請信號量,信號量就變為了0,這不就是一把嗎!控制著線程的開關

重新理解一下鎖:不就是認為自己的資源只有一份,申請鎖不就類似于二元信號量,信號量P操作,釋放鎖不就是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;             // 進程pidstd::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;// std::string test = "hello world, hello log";// std::unique_ptr<LogStrategy> logger_ptr = std::make_unique<ConsoleLogStrategy>();// // logger_ptr->SyncLog(test);// // std::unique_ptr<LogStrategy> logger_ptr = std::make_unique<FileLogStrategy>();// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());return 0;
}

Makefile

logger_test:main.ccg++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:rm -f logger_test

這里的構思非常的巧妙,本來想要輸出一條完整的日志信息,需要很復雜的操作,現在利用這樣的做法就可以用一行代碼輸出一條完整的日志信息。

下面我們就來看看是怎樣的做法呢?

在這里插入圖片描述

我們在外部類 Logger 里重載了運算符(),返回了一個 LogMessage 類的臨時對象

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

EnableConsoleStrategy();這個其實就是一個宏,這個宏是對于 Logger 類里面的兩個函數的簡便操作,LOG也是一個宏,是對于()運算符的重載函數的簡便操作

在這里插入圖片描述

所以,當調用了LOG宏之后會返回一個臨時對象,<<運算符重載函數是LogMessage類的一個成員函數,返回的是臨時對象的引用,因為,LOG宏返回一個LogMessage類的臨時對象,這個臨時對象又繼續調用了 << 運算符函數,繼續返回臨時對象的引用,以此類推,直到調用結束

臨時對象是具有常性的,它的生命周期在一條語句之后結束,所以可以返回臨時對象的引用

今天的文章分享到此結束,覺得不錯的伙伴給個一鍵三連吧。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921727.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921727.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921727.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

conda 創建環境嵌套報錯

使用conda create --prefix /path可以成功創建&#xff0c;有可能時默認路徑沖突導致的 conda config --show 發現&#xff1a; envs_dirs: /root/autodl-tmp/miniconda3/envs/envs_test/path/root/autodl-tmp/miniconda3/envs/root/.conda/envs 未顯式指定環境路徑&#xf…

低代碼核心原理總結

Web 低代碼平臺核心原理深度解析 1. 架構總覽 Web低代碼平臺的核心架構包含四個關鍵層次&#xff1a; class LowCodePlatform {constructor() {this.visualEditor new VisualEditor(); // 可視化編輯器this.metaDataEngine new MetaDataEngine(); // 元數據引擎this.code…

操作系統研發工作心得體會 - 于復雜性中構建秩序

在操作系統&#xff08;OS&#xff09;研發這片要求極致嚴謹與創新的工程深海中航行數載&#xff0c;我的角色從一個純粹的技術專家&#xff0c;逐漸演變為一個需要兼顧技術深度、系統廣度與團隊效能的復合型角色。這段旅程&#xff0c;讓我深刻體會到&#xff0c;構建一個成功…

Excel 表格 - Excel 減少干擾、專注于內容的查看方式

Excel 減少干擾、專注于內容的查看方式 1、隱藏元素 點擊 【視圖】 -> 取消勾選 【網格線】 -> 取消勾選 【編輯欄】 -> 取消勾選 【標題】2、全屏顯示 點擊 【功能區顯示選項】&#xff08;工具欄右下角小箭頭&#xff09; -> 點擊 【全屏模式】

C# Web API 前端傳入參數時間為Utc

Web API 前端傳入參數時間為Utc&#xff08;時間相差8個小時&#xff09;1.在Program.csbuilder.Services.AddControllers().AddJsonOptions(options > {// 序列化時將時間轉換為本地時間&#xff08;北京時間&#xff09;options.JsonSerializerOptions.Converters.Add(new…

AI Agent開發入門:Semantic Kernel構建智能郵件助手

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;AI Agent——下一代人機交互范式 在人工智能技術…

WebAssembly:開啟高性能 Web 應用的新篇章

在互聯網技術飛速發展的浪潮中&#xff0c;Web應用的性能一直是一個重要的優化目標。傳統的JavaScript雖然靈活便捷&#xff0c;但在處理CPU密集型任務時&#xff0c;其性能瓶頸日益凸顯&#xff0c;限制了Web應用在游戲、音視頻編輯、科學計算、圖像處理等高性能領域的深入發展…

001-003 產品經理-ML應用構建-ML應用范圍

001-003 產品經理-ML應用構建-ML應用范圍 時間&#xff1a;2025年09月08日14:48:01 備注&#xff1a;筆記回顧和復習&#xff0c;僅用于分享而非商用&#xff0c;引用內容若侵權請聯系并刪除。 文章目錄001-003 產品經理-ML應用構建-ML應用范圍導引 學習法則1 內容索引 產品經…

軟件測試錯題筆記

1.capitalize()表示將字符串第一個字符轉換為大寫 2.pop()方法&#xff1a;指定一個鍵&#xff08;key&#xff09;作為參數來刪除并返回對應的值&#xff0c;不傳入任何參數報錯。 3.測試方法&#xff1a;黑盒測試&#xff08;等價類劃分法、邊界值分析、因果圖分析&#xf…

【一文分享】安全數據交換系統是什么?哪款產品性價比高?

隨著數據價值的提升&#xff0c;其流動過程中的安全風險也與日俱增。內部核心數據泄露、外部攻擊、不合規傳輸導致的合規風險……這些問題如同懸在企業頭上的“達摩克利斯之劍”。正是在這樣的背景下&#xff0c;安全數據交換系統 應運而生&#xff0c;成為了保障數據安全流動的…

postgresql9.2.4 離線安裝

1、創建用戶[rootvkeep ~]# groupadd postgres [rootvkeep ~]# useradd -g postgres postgres -m -s /bin/bash [rootvkeep ~]# echo "Database123" | passwd --stdin postgres2、安裝依賴包[rootvkeep ~]# yum install gcc gcc-c zlib-devel readline readline-deve…

【C++設計模式】第三篇:觀察者模式(別名:發布-訂閱模式、模型-視圖模式、源-監聽器模式)

C設計模式系列文章目錄 【C設計模式】第一篇 C單例模式–懶漢與餓漢以及線程安全 【C設計模式】第二篇&#xff1a;策略模式&#xff08;Strategy&#xff09;–從基本介紹&#xff0c;內部原理、應用場景、使用方法&#xff0c;常見問題和解決方案進行深度解析 【C設計模式】…

運作管理學習筆記5-生產和服務設施的選址

運作管理-北京交通大學5.1.設施選址概述 設施選址是一個戰略性的決策&#xff0c;做這個決策的時候會投入比較多的資源&#xff0c;而且未來去改變選址的成本和代價也比較大。 5.1.1.設施選址的重要性 設施選址影響企業經營情況 設施選址對設施布局以及投產后的生產經營費用、產…

JUnit 詳解

一、JUnit 簡介&#xff1a;什么是 JUnit&#xff1f;為什么要用它&#xff1f;1.1 核心定義JUnit 是一個開源的、基于 Java 語言的單元測試框架&#xff0c;最初由 Erich Gamma (GoF 設計模式作者之一) 和 Kent Beck (極限編程創始人) 在 1997 年共同開發。作為 xUnit 測試框架…

數據結構造神計劃第三天---數據類型

&#x1f525;個人主頁&#xff1a;尋星探路 &#x1f3ac;作者簡介&#xff1a;Java研發方向學習者 &#x1f4d6;個人專欄&#xff1a;《從青銅到王者&#xff0c;就差這講數據結構&#xff01;&#xff01;&#xff01;》、 《JAVA&#xff08;SE&#xff09;----如此簡單&a…

AI API Tester體驗:API測試工具如何高效生成接口測試用例、覆蓋異常場景?

前陣子幫后端測試支付接口時&#xff0c;我算是徹底明白 “API 測試能磨掉半條命”—— 明明接口文檔里寫了十幾種參數組合&#xff0c;手動寫測試用例時要么漏了 “簽名過期” 的場景&#xff0c;要么忘了校驗 “金額超過限額” 的返回值&#xff0c;測到半夜還被開發吐槽 “你…

音頻驅動數字人人臉模型

1.LatentSync: Taming Audio-Conditioned Latent Diffusion Models for Lip Sync with SyncNet Supervision 字節 2024 文章地址&#xff1a;https://arxiv.org/pdf/2412.09262 代碼地址&#xff1a;https://github.com/bytedance/LatentSync 訓練推理都有 2.wan2.2-s2v …

CentOS部署ELK Stack完整指南

文章目錄&#x1f680; ELK Stack 部署詳解&#xff08;CentOS 7/8&#xff09;&#x1f4e6; 一、環境準備1. 關閉防火墻&#xff08;或開放端口&#xff09;2. 關閉 SELinux3. 安裝基礎依賴4. 驗證 Java&#x1f53d; 二、下載并安裝 ELK 組件1. 導入 Elastic GPG 密鑰2. 創建…

Spring Boot 攔截器(Interceptor)與過濾器(Filter)有什么區別?

在 Spring Boot 項目中&#xff0c;我們經常會遇到需要在請求處理前后執行一些通用邏輯的場景&#xff0c;比如記錄日志、權限校驗、全局異常處理等。此時&#xff0c;我們通常會面臨兩種選擇&#xff1a;過濾器&#xff08;Filter&#xff09; 和 攔截器&#xff08;Intercept…

【技術教程】如何將文檔編輯器集成至基于Java的Web應用程序

在如今的企業協作場景中&#xff0c;“文檔” 早已不是簡單的文字載體&#xff01;從項目需求文檔的多人實時修改&#xff0c;到財務報表的在線批注&#xff0c;再到合同草案的版本追溯&#xff0c;用戶越來越需要在 Web 應用內直接完成 “編輯 - 協作 - 存儲” 全流程。 但很…