日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++11实战——多线程的日志类

發布時間:2025/7/25 c/c++ 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++11实战——多线程的日志类 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

C++11實戰——多線程的日志類

C++標準庫的std::coutstd::ofstream重載了operator<<,單線程使用非常簡單。但由于其并非線程安全,在多線程中使用則需要自己加鎖同步,很是繁瑣。
形如“int printf ( const char * format, ... );”的 傳統C函數,雖然線程安全但使用上比 operator<< 麻煩的多。
本文將利用一些 C++11新特性 實現一個線程安全的日志類(兼顧寫文件日志和打印控制臺日志),并力求代碼壓縮在200行之內。

源碼下載:
github: github.com/FutaAlice/cpp11logger
csdn: download.csdn.net/download/u014755412/10252806


接口設計

外部接口需要盡可能簡單,我們先制定一個理想接口,然后完成其內部實現:

using namespace logger; int main() {// 控制臺日志ConsoleLogger debug;// 控制臺輸出,默認的安全等級(Debug)// output: [2017-02-29 00:00:00][Debug] Main thread, Message 0debug() << "Main thread, Message " << 0;// 控制臺輸出,安全等級為 Warning// output: [2017-02-29 00:00:00][Warning] Main thread, Message 1debug(Level::Warning) << "Main thread, Message " << 1;// 文件日志輸出位置FileLogger fl("message.log");// 寫文件日志,安全等級為 Info// output: [2017-02-29 00:00:00][Info] Main thread, Message 2fl(Level::Info) << "Main thread, Message num: " << 2; }

幾個重點問題的解決方式

1) 在哪里加鎖?

如果使用形如 debug(const char *fmt, ...) 的不定參數,我們可以將鎖放在函數首尾,保證其線程安全:

void debug(const char *fmt, ...) {static std::mutex m;m.lock();// do something.m.unlock(); }

設計接口考慮到調用的方便,采用了 operator<< 的方式,而非形如 (const char *fmt, ...) 的不定參數。
也就是說,調用中每出現一次 << ,operator<< 就會被調用一次。

ConsoleLogger debug; debug() << "Main thread " << 0; // operator<< 共調用2次

而對于一行中出現多次 <<符號 的調用而言,如果在 operator<< 重載函數的首尾加鎖,兩次 operator<< 之間依然會混入其他線程的日志信息。

ConsoleLogger debug; future<void> f(async([]{ debug() << "_fuck_"; })); f.get(); debug() << "Main thread " << 0; // 可能輸出結果 1:Main thread 0_fuck_ // 可能輸出結果 2:Main thread _fuck_0 // 可能輸出結果 3:_fuck_Main thread 0

那么保證線程安全鎖放在那里?(這里比較繞,看代碼比較清楚)

我們先重載日志類(ConsoleLogger)的 operator() ,使其返回一個文本緩沖區臨時對象
即,debug() 返回一個緩沖區,它是臨時的,沒有左值或右值引用接收它在行末分號處被銷毀
緩沖區重載 operator<< 接收文本信息并暫時保存,在其析構函數中:"日志類對象加鎖、寫日志、解鎖" 順序進行。

以此在保證調用接口簡單的同時實現線程安全。

2) 文本緩沖區有現成的輪子么?

C++標準庫的 ostringstream 是一個理想的緩沖區,它完整實現了 operator<< 。
我們只需要派生一個類,為其重寫析構函數即可。

3) 獲取日期時間有什么簡單的方法?

C++11新增的 chrono 庫,配合 localtime_r 函數(windows下為 localtime_s, 只有參數順序不同)

int localtime_r ( time_t *t,struct tm *tm ) // linux int localtime_s ( struct tm *tm, time_t *t ) // windows

注意通用的 struct tm *localtime(const time_t *clock) 函數不是線程安全的。

4) 如何防止用于指定日志等級的枚舉(enum Level)污染命名空間?

C/C++ 中具名(有名字)的 enum 類型的名字,以及 enum 的成員的名字都是全局可見的。
如果在相同的代碼域中的兩個枚舉類型具有相同名字的枚舉成員,這會導致命名沖突。
針對這些缺點,C++11引入了一種新的枚舉類型,即“枚舉類”,又稱“強類型枚舉”(strong-typed enum)。

namespace testenum { // 傳統的枚舉 enum Fuck { f1, f2, f3 };// C++11 強類型枚舉 enum class Shit { s1, s2, s3 }; }int main() {using namespace testenum;auto a = f1; // 通過,命名空間被 "enum Fuck" 成員污染auto b = s1; // 編譯報錯,未定義標識符 s1,"enum class Shit" 不會污染命名空間auto c = Shit::s1; // 通過int A = a; // 通過int C = c; // 編譯報錯,不允許隱式轉換為整型 }

如上代碼所示,強類型枚舉不會污染 namespace 并且 不會隱式轉換為整形。

日志類及測試工程實現代碼

目錄結構

-- logger
-- -- logger.h
-- -- logger.cpp
-- -- logger_test.cpp

源代碼

logger.h

// logger.h #pragma once #include <string> #include <fstream> #include <sstream> #include <mutex>struct tm;namespace logger { // 強類型枚舉,指定日志等級 enum class Level { Debug, Info, Warning, Error, Fatal }; class FileLogger; // 寫文檔用的日志類 class ConsoleLogger; // 控制臺輸出用的日志類 class BaseLogger; // 純虛基類class BaseLogger {class LogStream; // 用于文本緩沖的內部類聲明 public:BaseLogger() = default;virtual ~BaseLogger() = default;// 重載 operator() 返回緩沖區對象virtual LogStream operator()(Level nLevel = Level::Debug); private:const tm* getLocalTime();// 供緩沖區對象析構時調用(函數加鎖保證線程安全)void endline(Level nLevel, std::string&& oMessage);// 純虛函數,預留接口,由派生類實現virtual void output(const tm *p_tm,const char *str_level,const char *str_message) = 0; private:std::mutex _lock;tm _localTime; };// 用于文本緩沖區的類,繼承 std::ostringstream class BaseLogger::LogStream : public std::ostringstream {BaseLogger& m_oLogger;Level m_nLevel; public:LogStream(BaseLogger& oLogger, Level nLevel): m_oLogger(oLogger), m_nLevel(nLevel) {};LogStream(const LogStream& ls): m_oLogger(ls.m_oLogger), m_nLevel(ls.m_nLevel) {};~LogStream() // 為其重寫析構函數,在析構時打日志{m_oLogger.endline(m_nLevel, std::move(str()));} };// 控制臺輸出用的日志類 class ConsoleLogger : public BaseLogger {using BaseLogger::BaseLogger;virtual void output(const tm *p_tm,const char *str_level,const char *str_message); };// 寫文檔用的日志類 class FileLogger : public BaseLogger { public:FileLogger(std::string filename) noexcept;FileLogger(const FileLogger&) = delete;FileLogger(FileLogger&&) = delete;virtual ~FileLogger(); private:virtual void output(const tm *p_tm,const char *str_level,const char *str_message); private:std::ofstream _file; };extern ConsoleLogger debug; extern FileLogger record;} // namespace logger

logger.cpp

// logger.cpp #include <cassert> #include <chrono> #include <ctime> #include <iostream> #include <iomanip> #include <map> #include <regex> #include "logger.h"using namespace std; using namespace logger;ConsoleLogger logger::debug; FileLogger logger::record("build_at_" __DATE__ "_" __TIME__ ".log");#ifdef WIN32 #define localtime_r(_Time, _Tm) localtime_s(_Tm, _Time) #endifstatic const map<Level, const char *> LevelStr = {{ Level::Debug, "Debug" },{ Level::Info, "Info" },{ Level::Warning, "Warning" },{ Level::Error, "Error" },{ Level::Fatal, "Fatal" }, };ostream& operator<< (ostream& stream, const tm* tm) {return stream << 1900 + tm->tm_year << '-'<< setfill('0') << setw(2) << tm->tm_mon + 1 << '-'<< setfill('0') << setw(2) << tm->tm_mday << ' '<< setfill('0') << setw(2) << tm->tm_hour << ':'<< setfill('0') << setw(2) << tm->tm_min << ':'<< setfill('0') << setw(2) << tm->tm_sec; }BaseLogger::LogStream BaseLogger::operator()(Level nLevel) {return LogStream(*this, nLevel); }const tm* BaseLogger::getLocalTime() {auto now = chrono::system_clock::now();auto in_time_t = chrono::system_clock::to_time_t(now);localtime_r(&in_time_t, &_localTime);return &_localTime; }void BaseLogger::endline(Level nLevel, string&& oMessage) {_lock.lock();output(getLocalTime(), LevelStr.find(nLevel)->second, oMessage.c_str());_lock.unlock(); }void ConsoleLogger::output(const tm *p_tm,const char *str_level,const char *str_message) {cout << '[' << p_tm << ']'<< '[' << str_level << "]"<< "\t" << str_message << endl;cout.flush(); }FileLogger::FileLogger(string filename) noexcept: BaseLogger() {string valid_filename(filename.size(), '\0');regex express("/|:| |>|<|\"|\\*|\\?|\\|");regex_replace(valid_filename.begin(),filename.begin(),filename.end(),express,"_");_file.open(valid_filename,fstream::out | fstream::app | fstream::ate);assert(!_file.fail()); }FileLogger::~FileLogger() {_file.flush();_file.close(); }void FileLogger::output(const tm *p_tm,const char *str_level,const char *str_message) {_file << '[' << p_tm << ']'<< '[' << str_level << "]"<< "\t" << str_message << endl;_file.flush(); }

logger_test.cpp

// logger_test.cpp #include <thread> #include <list> #include "logger.h"using namespace std; using namespace logger;int main() {list<shared_ptr<thread>> oThreads;ConsoleLogger ocl;ocl << "test" << "log";FileLogger ofl("shit.log");ofl << "test" << "log";/** 控制臺輸出*/for (int i = 0; i < 10; i++){oThreads.push_back(shared_ptr<thread>(new thread([=]() {for (int j = 0; j < 100; ++j)debug() << "Thread " << i << ", Message " << j;})));}for (int i = 0; i < 100; i++)debug() << "Main thread, Message " << i;for (auto oThread : oThreads)oThread->join();debug(Level::Info) << "output to console, done.";debug(Level::Info) << "press any to continue this test.";getchar();oThreads.clear();/** 日志文檔輸出*/for (int i = 0; i < 10; i++){oThreads.push_back(shared_ptr<thread>(new thread([=]() {for (int j = 0; j < 100; ++j)record() << "Thread " << i << ", Message " << j;})));}for (int i = 0; i < 100; i++)record() << "Main thread, Message " << i;for (auto oThread : oThreads)oThread->join();debug(Level::Info) << "done.";getchar();return 0; }

源碼下載

github: github.com/FutaAlice/cpp11logger
csdn: download.csdn.net/download/u014755412/10252806

轉載于:https://www.cnblogs.com/FutaAlice/p/9041725.html

總結

以上是生活随笔為你收集整理的C++11实战——多线程的日志类的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。