微网站建设企划书制作网站心得

news/2025/10/8 13:55:36/文章来源:
微网站建设企划书,制作网站心得,怎么把网站改为正在建设中,wordpress 安装路径转载#xff1a;C设计实现日志系统 - 知乎 (zhihu.com) 日志系统几乎是每一个实际的软件项目从开发、测试到交付#xff0c;再到后期的维护过程中极为重要的 查看软件代码运行流程、 还原错误现场、 记录运行错误位置及上下文等的重要依据。一个高性能的日志系统#xff0c…转载C设计实现日志系统 - 知乎 (zhihu.com)  日志系统几乎是每一个实际的软件项目从开发、测试到交付再到后期的维护过程中极为重要的 查看软件代码运行流程、 还原错误现场、 记录运行错误位置及上下文等的重要依据。一个高性能的日志系统能够准确记录重要的变量信息同时又没有冗余的打印导致日志文件记录无效的数据。本文Jungle将用C设计实现一个日志系统。 1.为什么需要日志 为什么需要日志其实在引言中已经提到了实际的软件项目的几乎每个过程都离不开日志。初学代码时Jungle的第一行代码是实现打印“hello world”打印到控制台。在后来的学习中Jungle又学会了设断点调试代码在适当的地方通过断点来观察变量的值。但在实际的软件项目中试想一下通过输出到控制台或者通过设断点来调试代码可能吗 客户现场会让你现场打印到控制台上调试吗报了error的软件项目你能够明确知道软件crash的位置吗你能保证设断点可以还原error时候的现场吗概率性的error事件设断点还奏效吗如果是时效性的代码比如USB连接 设断点调试还合理吗…… 日志可以记录每一时刻软件的运行情况记录error或者crash时的信息时间、关键变量的值、出错位置、线程等另一方面对于概率性error事件可以在重复测试时通过日志来查询错误复现时候的情况。简言之日志是跟踪和回忆某个时刻或者时间段内的程序行为进而定位问题的一种重要手段。 2.日志系统设计 软件运行过程中需要记录的有什么呢前述已经提到关键变量的值、运行的位置哪个文件、哪个函数、哪一行、时间、线程号、进程号。本文Jungle采用C设计了LOG类介绍LOG类的设计之前需要提及的是log的级别和log位置。 2.1.1.log级别 Log级别是什么意思呢在开发阶段Jungle可能想尽可能详细地跟踪代码运行过程所以可以打印尽可能多的信息到日志文件中测试过程中测试部可能不需要这么详细的信息所以这时候有的信息可能不必输出到Log文件产品交付客户使用时为了软件运行更快、客户体验更好这时候就只需打印关键信息到日志文件了因为过多的写文件会耗费大量时间影响软件运行速度。所以Jungle为LOG类定义了如下级别 enum LOGLEVEL {LOG_LEVEL_NONE,LOG_LEVEL_ERROR, // errorLOG_LEVEL_WARNING, // warningLOG_LEVEL_DEBUG, // debugLOG_LEVEL_INFO, // info }; 在软件设计中可以通过某些方法或者预留一些开关来设置Log级别方便在开发、调试、测试和客户现场灵活地调整日志级别以获取到有用的日志信息。 2.1.2.log输出位置 Log文件可以输出到控制台其实也是不错的方法也可以输出到指定路径下的某个文件里也可能有别的需求。比如开发或调试时简单的信息直接就打印到软件某个界面上测试或者交付客户时最好将日志保存到文件里这样可以保存尽可能多的信息。因此Jungle进行了如下设计 enum LOGTARGET {LOG_TARGET_NONE 0x00,LOG_TARGET_CONSOLE 0x01,LOG_TARGET_FILE 0x10 }; 2.1.3.log的作用域 一个软件系统要在哪儿输出日志呢Everywhere只要是你想打印日志的地方任何一个函数、任何一个文件都应该而且必须可以打印。也就是说这个log类的对象不妨叫做日志记录器日志记录器必须是全局的 光是全局的就够了吗你这个文件里有一个全局的日志记录器输出日志到file.log文件里另一个文件里也有一个日志记录器也输出到file.log文件里……多个日志记录器同时往一个文件里写日志这显然不合理。所以还必须保证日志记录器全局且唯一 怎么保证日志记录器唯一呢即Log类在具体的软件系统中有且仅有一个实例化对象。答案是采用单例模式设计模式九——单例模式 2.2.日志类的设计 综上所述Jungle设计的日志类LOG如下 class LOG { public:// 初始化void init(LOGLEVEL loglevel, LOGTARGET logtarget);// void uninit();// fileint createFile();static LOG* getInstance();// Log级别LOGLEVEL getLogLevel();void setLogLevel(LOGLEVEL loglevel);// Log输出位置LOGTARGET getLogTarget();void setLogTarget(LOGTARGET logtarget);// 打logstatic int writeLog(LOGLEVEL loglevel, // Log级别unsigned char* fileName, // 函数所在文件名unsigned char* function, // 函数名int lineNumber, // 行号char* format, // 格式化...); // 变量// 输出logstatic void outputToTarget();private:LOG();~LOG();static LOG* Log;// 互斥锁static mutex log_mutex;// 存储log的bufferstatic string logBuffer;// Log级别LOGLEVEL logLevel;// Log输出位置LOGTARGET logTarget;// Handlestatic HANDLE mFileHandle; }; 其中互斥锁log_mutex是用于在多线程环境下保证只创建一个LOG类的实例 设计模式九——单例模式mFileHandle是log文件的句柄。 2.3.日志类的实现 2.3.1.初始化 LOG* LOG::Log NULL; string LOG::logBuffer ; HANDLE LOG::mFileHandle INVALID_HANDLE_VALUE; mutex LOG::log_mutex;LOG::LOG() {// 初始化init(LOG_LEVEL_NONE, LOG_TARGET_FILE); } void LOG::init(LOGLEVEL loglevel, LOGTARGET logtarget) {setLogLevel(loglevel);setLogTarget(logtarget);createFile(); }void LOG::uninit() {if (INVALID_HANDLE_VALUE ! mFileHandle){CloseHandle(mFileHandle);} }LOG* LOG::getInstance() {if (NULL Log){log_mutex.lock();if (NULL Log){Log new LOG();}log_mutex.unlock();}return Log; }LOGLEVEL LOG::getLogLevel() {return this-logLevel; }void LOG::setLogLevel(LOGLEVEL iLogLevel) {this-logLevel iLogLevel; }LOGTARGET LOG::getLogTarget() {return this-logTarget; }void LOG::setLogTarget(LOGTARGET iLogTarget) {this-logTarget iLogTarget; } 初始化工作设置了日志的级别和输出位置代码中提供了日志级别和输出位置的setter、getter方法。函数createFile()是创建日志文件位置并获取日志文件的句柄mFileHandle。代码如下 int LOG::createFile() {TCHAR fileDirectory[256];GetCurrentDirectory(256, fileDirectory);// 创建log文件的路径TCHAR logFileDirectory[256];_stprintf_s(logFileDirectory, _T(%s\\Test\\), fileDirectory);// 使用_stprintf_s需要包含头文件TCHAR.H// 文件夹不存在则创建文件夹if (_taccess(logFileDirectory, 0) -1){_tmkdir(logFileDirectory);}TCHAR cTmpPath[MAX_PATH] { 0 };TCHAR* lpPos NULL;TCHAR cTmp _T(\0);WCHAR pszLogFileName[256];// wcscat:连接字符串wcscat(logFileDirectory, _T(test.log));_stprintf_s(pszLogFileName, _T(%s), logFileDirectory);mFileHandle CreateFile(pszLogFileName,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);if (INVALID_HANDLE_VALUE mFileHandle){return -1;}return 0; } 其中需要介绍的是下述函数 GetCurrentDirectory在一个缓冲区中装载当前目录_stprintf_s将若干个参数按照format格式存到buffer中_taccess判断文件是否存在返回值0表示该文件存在返回-1表示文件不存在或者该模式下没有访问权限_tmkdir创建一个目录 2.3.2.写日志 以下是writeLog()方法的实现 int LOG::writeLog(LOGLEVEL loglevel, // Log级别unsigned char* fileName, // 函数所在文件名unsigned char* function, // 函数名int lineNumber, // 行号char* format, // 格式化...) {int ret 0;// 获取日期和时间char timeBuffer[100];ret getSystemTime(timeBuffer);logBuffer string(timeBuffer);// LOG级别char* logLevel;if (loglevel LOG_LEVEL_DEBUG){logLevel DEBUG;}else if (loglevel LOG_LEVEL_INFO){logLevel INFO;}else if (loglevel LOG_LEVEL_WARNING){logLevel WARNING;}else if (loglevel LOG_LEVEL_ERROR){logLevel ERROR;}// [进程号][线程号][Log级别][文件名][函数名:行号]char locInfo[100];char* format2 [PID:%4d][TID:%4d][%s][%-s][%s:%4d];ret printfToBuffer(locInfo, 100, format2,GetCurrentProcessId(),GetCurrentThreadId(),logLevel,fileName,function,lineNumber);logBuffer string(locInfo); // 日志正文char logInfo2[256];va_list ap;va_start(ap, format);ret vsnprintf(logInfo2, 256, format, ap);va_end(ap);logBuffer string(logInfo2);logBuffer string(\n);outputToTarget();return 0; } 2.3.3.输出日志 void LOG::outputToTarget() {if (LOG::getInstance()-getLogTarget() LOG_TARGET_FILE){SetFilePointer(mFileHandle, 0, NULL, FILE_END);DWORD dwBytesWritten 0;WriteFile(mFileHandle, logBuffer.c_str(), logBuffer.length(), dwBytesWritten, NULL);FlushFileBuffers(mFileHandle);}if (LOG::getInstance()-getLogTarget() LOG_TARGET_CONSOLE){printf(%s, logBuffer.c_str());}// 清除bufferlogBuffer.clear(); } SetFilePointer将文件指针移动到文件指定的位置FlushFileBuffers把写文件缓冲区的数据强制写入磁盘 为了使用方便可以定义一些宏来简化函数的使用本文不再赘述。 3.测试 Jungle将上述设计实现的日志系统应用到了之前写的一些小程序里比如在之前的“欲戴王冠必承其重”——深度解析职责链模式的代码。如何添加呢就是将两个文件头文件和源文件加入工程包含头文件再在需要打log的地方加上Jungle在日志类里定义的宏即可。下列是示例log 因为程序比较简单代码量很小所以只有一个线程log中TID都是一样的。但上述测试结果验证了Jungle设计的日志系统是可行的。 4.多线程环境 4.1.多线程环境测试 接下来Jungle设计一个简单的多线程环境测试一下上述日志系统测试代码如下 #define THREAD_NUM 5 // 全局资源变量 int g_num 0;unsigned int __stdcall func(void *pPM) {LOG_INFO(enter);Sleep(50);g_num;LOG_INFO(g_num %d, g_num);LOG_INFO(exit);return 0; }int main() {LOG *logger LOG::getInstance();HANDLE handle[THREAD_NUM];//线程编号int threadNum 0;while (threadNum THREAD_NUM){handle[threadNum] (HANDLE)_beginthreadex(NULL, 0, func, NULL, 0, NULL);//等子线程接收到参数时主线程可能改变了这个i的值threadNum;}//保证子线程已全部运行结束WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);return 0; } 上述代码中Jungle一共开启了5个线程理论上打印的日志文件里TID应该出现5个不同的数值。每个线程里打印全局变量即全局共享资源的值。下面是输出的日志一共运行了两次第5、6行隔开 问题来啦 首先在第一次运行输出的日志里出现了乱码第1行和第4行而且看起来该输出log的地方没有完全输出真的吗 其次在第二次运行输出的日志里一行log里好像打印了两次日志第8行 问题出在哪里呢 为什么会出现乱码仔细看第8行log其实打印的都是同一个时刻、同一个位置都是在调用writeLog函数宏LOG_INFO即是调用writeLog函数时出现的问题也就是说在这个时刻两个线程都跑到函数writeLog里写log导致logBuffer缓冲区里存放了两次信息。只不过第8行运气较好每次的编码都保存完整。而第1行和第4行就没这么走运了logBuffer里已经完全乱了所以根本问题是多个线程在同一个时刻访问了同一个资源所以针对多线程环境我们需要做到共享资源的互斥 4.2.线程安全的日志系统 在单例模式的设计实现里已经提到了线程安全Jungle用互斥锁达到了互斥的目的。本文也可以使用互斥锁并且在日志对象实例的单例模式中已经使用但在这里Jungle想用另一种方法临界区。 在Log类成员里声明一个CRITICAL_SECTION对象criticalSection初始化时 InitializeCriticalSection(criticalSection); 当然最好在释放资源时加上下述代码 DeleteCriticalSection(criticalSection); 而在进入writeLog时和离开writeLog时加上下述代码 int LOG::writeLog(...) {int ret 0;EnterCriticalSection(criticalSection);// do somethingLeaveCriticalSection(criticalSection);return 0; } 需要提及的是最好是在LeaveCriticalSection之后再DeleteCriticalSection。 接下来再在多线程环境里测试Jungle测试了几次但为了缩短篇幅只展示一次的结果 可以看到日志完整记录了每个线程的运行过程线程号TID不同。 5.注意事项 尽管上述已经基本实现了日志系统但仍有很大的改进空间在调试代码和查阅资料的过程中Jungle发现需要注意以下几个问题 1字符编码问题宽字符、ANSI编码等多种不同编码的兼容 2Visio Studio版本的差异Jungle本想将日志系统应用到之前设计的一个机器人仿真控制器里但遗憾的是编译不通过因为那个代码是用Visio Studio 2008写的而Mutex是C2011标准的内容需要用支持该新标准的编译器比如VS2012及以上版本。当然了可以用临界区等其他方法实现互斥这里Jungle只是提出这个需要注意的问题 3关于宏_CRT_SECURE_NO_WARNINGS是的需要在预处理器里加上这个宏或者代码里显示声明这个宏否则编译不通过如下图。原因是代码中使用的wcscat等函数不安全可能会造成内存泄露等。解决方法除了前述提到的声明宏以外还可以使用更安全的函数 源码地址 日志系统几乎是每一个实际的软件项目从开发、测试到交付再到后期的维护过程中极为重要的 查看软件代码运行流程、 还原错误现场、 记录运行错误位置及上下文等的重要依据。一个高性能的日志系统能够准确记录重要的变量信息同时又没有冗余的打印导致日志文件记录无效的数据。本文Jungle将用C设计实现一个日志系统。 1.为什么需要日志 为什么需要日志其实在引言中已经提到了实际的软件项目的几乎每个过程都离不开日志。初学代码时Jungle的第一行代码是实现打印“hello world”打印到控制台。在后来的学习中Jungle又学会了设断点调试代码在适当的地方通过断点来观察变量的值。但在实际的软件项目中试想一下通过输出到控制台或者通过设断点来调试代码可能吗 客户现场会让你现场打印到控制台上调试吗报了error的软件项目你能够明确知道软件crash的位置吗你能保证设断点可以还原error时候的现场吗概率性的error事件设断点还奏效吗如果是时效性的代码比如USB连接 设断点调试还合理吗…… 日志可以记录每一时刻软件的运行情况记录error或者crash时的信息时间、关键变量的值、出错位置、线程等另一方面对于概率性error事件可以在重复测试时通过日志来查询错误复现时候的情况。简言之日志是跟踪和回忆某个时刻或者时间段内的程序行为进而定位问题的一种重要手段。 2.日志系统设计 软件运行过程中需要记录的有什么呢前述已经提到关键变量的值、运行的位置哪个文件、哪个函数、哪一行、时间、线程号、进程号。本文Jungle采用C设计了LOG类介绍LOG类的设计之前需要提及的是log的级别和log位置。 2.1.1.log级别 Log级别是什么意思呢在开发阶段Jungle可能想尽可能详细地跟踪代码运行过程所以可以打印尽可能多的信息到日志文件中测试过程中测试部可能不需要这么详细的信息所以这时候有的信息可能不必输出到Log文件产品交付客户使用时为了软件运行更快、客户体验更好这时候就只需打印关键信息到日志文件了因为过多的写文件会耗费大量时间影响软件运行速度。所以Jungle为LOG类定义了如下级别 enum LOGLEVEL {LOG_LEVEL_NONE,LOG_LEVEL_ERROR, // errorLOG_LEVEL_WARNING, // warningLOG_LEVEL_DEBUG, // debugLOG_LEVEL_INFO, // info }; 在软件设计中可以通过某些方法或者预留一些开关来设置Log级别方便在开发、调试、测试和客户现场灵活地调整日志级别以获取到有用的日志信息。 2.1.2.log输出位置 Log文件可以输出到控制台其实也是不错的方法也可以输出到指定路径下的某个文件里也可能有别的需求。比如开发或调试时简单的信息直接就打印到软件某个界面上测试或者交付客户时最好将日志保存到文件里这样可以保存尽可能多的信息。因此Jungle进行了如下设计 enum LOGTARGET {LOG_TARGET_NONE 0x00,LOG_TARGET_CONSOLE 0x01,LOG_TARGET_FILE 0x10 }; 2.1.3.log的作用域 一个软件系统要在哪儿输出日志呢Everywhere只要是你想打印日志的地方任何一个函数、任何一个文件都应该而且必须可以打印。也就是说这个log类的对象不妨叫做日志记录器日志记录器必须是全局的 光是全局的就够了吗你这个文件里有一个全局的日志记录器输出日志到file.log文件里另一个文件里也有一个日志记录器也输出到file.log文件里……多个日志记录器同时往一个文件里写日志这显然不合理。所以还必须保证日志记录器全局且唯一 怎么保证日志记录器唯一呢即Log类在具体的软件系统中有且仅有一个实例化对象。答案是采用单例模式设计模式九——单例模式 2.2.日志类的设计 综上所述Jungle设计的日志类LOG如下 class LOG { public:// 初始化void init(LOGLEVEL loglevel, LOGTARGET logtarget);// void uninit();// fileint createFile();static LOG* getInstance();// Log级别LOGLEVEL getLogLevel();void setLogLevel(LOGLEVEL loglevel);// Log输出位置LOGTARGET getLogTarget();void setLogTarget(LOGTARGET logtarget);// 打logstatic int writeLog(LOGLEVEL loglevel, // Log级别unsigned char* fileName, // 函数所在文件名unsigned char* function, // 函数名int lineNumber, // 行号char* format, // 格式化...); // 变量// 输出logstatic void outputToTarget();private:LOG();~LOG();static LOG* Log;// 互斥锁static mutex log_mutex;// 存储log的bufferstatic string logBuffer;// Log级别LOGLEVEL logLevel;// Log输出位置LOGTARGET logTarget;// Handlestatic HANDLE mFileHandle; }; 其中互斥锁log_mutex是用于在多线程环境下保证只创建一个LOG类的实例 设计模式九——单例模式mFileHandle是log文件的句柄。 2.3.日志类的实现 2.3.1.初始化 LOG* LOG::Log NULL; string LOG::logBuffer ; HANDLE LOG::mFileHandle INVALID_HANDLE_VALUE; mutex LOG::log_mutex;LOG::LOG() {// 初始化init(LOG_LEVEL_NONE, LOG_TARGET_FILE); } void LOG::init(LOGLEVEL loglevel, LOGTARGET logtarget) {setLogLevel(loglevel);setLogTarget(logtarget);createFile(); }void LOG::uninit() {if (INVALID_HANDLE_VALUE ! mFileHandle){CloseHandle(mFileHandle);} }LOG* LOG::getInstance() {if (NULL Log){log_mutex.lock();if (NULL Log){Log new LOG();}log_mutex.unlock();}return Log; }LOGLEVEL LOG::getLogLevel() {return this-logLevel; }void LOG::setLogLevel(LOGLEVEL iLogLevel) {this-logLevel iLogLevel; }LOGTARGET LOG::getLogTarget() {return this-logTarget; }void LOG::setLogTarget(LOGTARGET iLogTarget) {this-logTarget iLogTarget; } 初始化工作设置了日志的级别和输出位置代码中提供了日志级别和输出位置的setter、getter方法。函数createFile()是创建日志文件位置并获取日志文件的句柄mFileHandle。代码如下 int LOG::createFile() {TCHAR fileDirectory[256];GetCurrentDirectory(256, fileDirectory);// 创建log文件的路径TCHAR logFileDirectory[256];_stprintf_s(logFileDirectory, _T(%s\\Test\\), fileDirectory);// 使用_stprintf_s需要包含头文件TCHAR.H// 文件夹不存在则创建文件夹if (_taccess(logFileDirectory, 0) -1){_tmkdir(logFileDirectory);}TCHAR cTmpPath[MAX_PATH] { 0 };TCHAR* lpPos NULL;TCHAR cTmp _T(\0);WCHAR pszLogFileName[256];// wcscat:连接字符串wcscat(logFileDirectory, _T(test.log));_stprintf_s(pszLogFileName, _T(%s), logFileDirectory);mFileHandle CreateFile(pszLogFileName,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);if (INVALID_HANDLE_VALUE mFileHandle){return -1;}return 0; } 其中需要介绍的是下述函数 GetCurrentDirectory在一个缓冲区中装载当前目录_stprintf_s将若干个参数按照format格式存到buffer中_taccess判断文件是否存在返回值0表示该文件存在返回-1表示文件不存在或者该模式下没有访问权限_tmkdir创建一个目录 2.3.2.写日志 以下是writeLog()方法的实现 int LOG::writeLog(LOGLEVEL loglevel, // Log级别unsigned char* fileName, // 函数所在文件名unsigned char* function, // 函数名int lineNumber, // 行号char* format, // 格式化...) {int ret 0;// 获取日期和时间char timeBuffer[100];ret getSystemTime(timeBuffer);logBuffer string(timeBuffer);// LOG级别char* logLevel;if (loglevel LOG_LEVEL_DEBUG){logLevel DEBUG;}else if (loglevel LOG_LEVEL_INFO){logLevel INFO;}else if (loglevel LOG_LEVEL_WARNING){logLevel WARNING;}else if (loglevel LOG_LEVEL_ERROR){logLevel ERROR;}// [进程号][线程号][Log级别][文件名][函数名:行号]char locInfo[100];char* format2 [PID:%4d][TID:%4d][%s][%-s][%s:%4d];ret printfToBuffer(locInfo, 100, format2,GetCurrentProcessId(),GetCurrentThreadId(),logLevel,fileName,function,lineNumber);logBuffer string(locInfo); // 日志正文char logInfo2[256];va_list ap;va_start(ap, format);ret vsnprintf(logInfo2, 256, format, ap);va_end(ap);logBuffer string(logInfo2);logBuffer string(\n);outputToTarget();return 0; } 2.3.3.输出日志 void LOG::outputToTarget() {if (LOG::getInstance()-getLogTarget() LOG_TARGET_FILE){SetFilePointer(mFileHandle, 0, NULL, FILE_END);DWORD dwBytesWritten 0;WriteFile(mFileHandle, logBuffer.c_str(), logBuffer.length(), dwBytesWritten, NULL);FlushFileBuffers(mFileHandle);}if (LOG::getInstance()-getLogTarget() LOG_TARGET_CONSOLE){printf(%s, logBuffer.c_str());}// 清除bufferlogBuffer.clear(); } SetFilePointer将文件指针移动到文件指定的位置FlushFileBuffers把写文件缓冲区的数据强制写入磁盘 为了使用方便可以定义一些宏来简化函数的使用本文不再赘述。 3.测试 Jungle将上述设计实现的日志系统应用到了之前写的一些小程序里比如在之前的“欲戴王冠必承其重”——深度解析职责链模式的代码。如何添加呢就是将两个文件头文件和源文件加入工程包含头文件再在需要打log的地方加上Jungle在日志类里定义的宏即可。下列是示例log 因为程序比较简单代码量很小所以只有一个线程log中TID都是一样的。但上述测试结果验证了Jungle设计的日志系统是可行的。 4.多线程环境 4.1.多线程环境测试 接下来Jungle设计一个简单的多线程环境测试一下上述日志系统测试代码如下 #define THREAD_NUM 5 // 全局资源变量 int g_num 0;unsigned int __stdcall func(void *pPM) {LOG_INFO(enter);Sleep(50);g_num;LOG_INFO(g_num %d, g_num);LOG_INFO(exit);return 0; }int main() {LOG *logger LOG::getInstance();HANDLE handle[THREAD_NUM];//线程编号int threadNum 0;while (threadNum THREAD_NUM){handle[threadNum] (HANDLE)_beginthreadex(NULL, 0, func, NULL, 0, NULL);//等子线程接收到参数时主线程可能改变了这个i的值threadNum;}//保证子线程已全部运行结束WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);return 0; } 上述代码中Jungle一共开启了5个线程理论上打印的日志文件里TID应该出现5个不同的数值。每个线程里打印全局变量即全局共享资源的值。下面是输出的日志一共运行了两次第5、6行隔开 问题来啦 首先在第一次运行输出的日志里出现了乱码第1行和第4行而且看起来该输出log的地方没有完全输出真的吗 其次在第二次运行输出的日志里一行log里好像打印了两次日志第8行 问题出在哪里呢 为什么会出现乱码仔细看第8行log其实打印的都是同一个时刻、同一个位置都是在调用writeLog函数宏LOG_INFO即是调用writeLog函数时出现的问题也就是说在这个时刻两个线程都跑到函数writeLog里写log导致logBuffer缓冲区里存放了两次信息。只不过第8行运气较好每次的编码都保存完整。而第1行和第4行就没这么走运了logBuffer里已经完全乱了所以根本问题是多个线程在同一个时刻访问了同一个资源所以针对多线程环境我们需要做到共享资源的互斥 4.2.线程安全的日志系统 在单例模式的设计实现里已经提到了线程安全Jungle用互斥锁达到了互斥的目的。本文也可以使用互斥锁并且在日志对象实例的单例模式中已经使用但在这里Jungle想用另一种方法临界区。 在Log类成员里声明一个CRITICAL_SECTION对象criticalSection初始化时 InitializeCriticalSection(criticalSection); 当然最好在释放资源时加上下述代码 DeleteCriticalSection(criticalSection); 而在进入writeLog时和离开writeLog时加上下述代码 int LOG::writeLog(...) {int ret 0;EnterCriticalSection(criticalSection);// do somethingLeaveCriticalSection(criticalSection);return 0; } 需要提及的是最好是在LeaveCriticalSection之后再DeleteCriticalSection。 接下来再在多线程环境里测试Jungle测试了几次但为了缩短篇幅只展示一次的结果 可以看到日志完整记录了每个线程的运行过程线程号TID不同。 5.注意事项 尽管上述已经基本实现了日志系统但仍有很大的改进空间在调试代码和查阅资料的过程中Jungle发现需要注意以下几个问题 1字符编码问题宽字符、ANSI编码等多种不同编码的兼容 2Visio Studio版本的差异Jungle本想将日志系统应用到之前设计的一个机器人仿真控制器里但遗憾的是编译不通过因为那个代码是用Visio Studio 2008写的而Mutex是C2011标准的内容需要用支持该新标准的编译器比如VS2012及以上版本。当然了可以用临界区等其他方法实现互斥这里Jungle只是提出这个需要注意的问题 3关于宏_CRT_SECURE_NO_WARNINGS是的需要在预处理器里加上这个宏或者代码里显示声明这个宏否则编译不通过如下图。原因是代码中使用的wcscat等函数不安全可能会造成内存泄露等。解决方法除了前述提到的声明宏以外还可以使用更安全的函数 源码地址 https://github.com/FengJungle/Log​github.com/FengJungle/Log

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/931575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在线教育网站建设策划WordPress自定义icon

目录 图像识别简介 神经网络 感知器 前馈神经网络 自动编码器 受限玻尔兹曼机 深度卷积网络 理解图像内容以及图像含义方面,计算机遇到了很大困难。本章先介绍计算机理解图像教育方面 遇到的难题,接着重点讲解一个基于深度学习的解决方法。我们会…

1000th post Problem 1

原题链接:https://artofproblemsolving.com/community/c4h3685828_1000th_post P1解法: 注意到有 \(\sum_{i=1}^n \frac{a_i}{s-a_i}=\sum_{i=1}^n\left(\frac{s}{b_i}-1\right)=\sum_{i=1}^n \frac{s}{b_i}+\sum_{i…

下行经济周期,就应该做只能在下行周期里做的事情

正文经济下行的时候,不要想着逆势而上,而要想着做那些只有在下行周期里才能做的事情。大部分人在经济不好的时候,要么焦虑,要么抱怨,要么等待。但是这些经济不好的时候,真的都是所谓的 Rubbish Time(垃圾时间)…

WPF Prism.Wpf RegionManager

Install-Package Prism.Wpf; Install-Package Prism.DryIOC; <Window x:Class="WpfApp28.Views.MainWin"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="ht…

题解:AT_agc065_d [AGC065D] Not Intersect

很牛的题。 题意:很简单了,不再赘述。 做法: 首先需要一个 Raney 引理:对于整数序列 \(a\),若 \(\sum a = 1\),则有且仅有一个 \(a\) 的循环位移满足前缀和均大于 \(0\)。 来简单证明一下,首先不会有两个及以上…

uniapp滚动导航 - unique

下面是效果图下面是实现代码<template><view class="container"><!-- 顶部导航 --><view class="navbar"><viewv-for="(item, index) in navList":key=&quo…

公司网站运营淮北建网站

一、概念 Java 内存区域和内存模型是不一样的东西&#xff0c;内存区域是指 Jvm 运行时将数据分区域存储&#xff0c;强调对内存空间的划分。 而内存模型&#xff08;Java Memory Model&#xff0c;简称 JMM &#xff09;是定义了线程和主内存之间的抽象关系&#xff0c;即 J…

三门网站制作峡江网站建设

背景&#xff1a;之前资产信息用网络接口进行数据推送&#xff0c;但是接口推送需要验证而且反应较慢。Kafak中间件提供了另一种可行的数据推送方式&#xff0c;它可以进行消息队列推送&#xff0c;且反应速度快。但是Kafka需部署在公网环境&#xff0c;并进行登录验证&#xf…

网站建设与维护视频教程篡改 网站 支付接口

面试经典&#xff08;4/150&#xff09;删除有序数组中的重复项 II 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c; 返回删除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须在 原…

滚动导航 - unique

下面是效果图下面是实现代码<!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8" /><title>滚动导航 Demo</title><style>body {margin:…

windows剪切板工具

列表 不推荐 ditto 界面我不喜欢,作为一个前台交互软件,用户界面搞得这么小,图片都看不清。CrossPaste 还行,但是没有标签或者说分类。 也没有导出功能。 不知道为啥,窗口总是不置顶。 提供了多种类型,算是比较现…

C#基础:启用线程池执行并行任务

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做字素的网站wordpress get option

从各方面来看&#xff0c;互联网向 IPv6 的过渡是件很缓慢的事情。不过在最近几年&#xff0c;可能是由于 IPv4 地址资源的枯竭&#xff0c;IPv6 的使用处于上升态势。相应的&#xff0c;开发者也有兴趣确保软件能在 IPv4 和 IPv6 下工作。但是&#xff0c;正如近期 OpenBSD 邮…

P1545 [USACO04DEC] Dividing the Path G 题解

P1545 [USACO04DEC] Dividing the Path G 题解 最近开始快刷蓝紫黑了,做完会写题解交上来。 题目传送门 题意 一条长为 \(L(1 \le L \le 10^6 , 2 | L)\) 的线段上,给出 \(N(1 \le N \le 10^3)\) 个可能相交的子段 \…

AJ-Report - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

视频采集程序

项目结构:VideoCaptureApp.pro QT += core gui multimedia multimediawidgetsgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11win32 { LIBS += -L$$PWD/lib/SDL2/lib/x64 \-L$$PWD/lib/ffmpeg…

怎么做网站弄网盟wordpress qq微博

【客户行业】金属加工行业 【问题类型】薪酬体系/激励体系 【客户背景】 某大型金属加工企业位于河北地区&#xff0c;成立于2000年&#xff0c;隶属于某大型有色金属集团&#xff0c;是一家集科研、开发、生产、销售于一体的国有企业&#xff0c;人员达到1000人。经过多年…

50m专线做视频网站青羊区城乡建设网站

很多高手都喜欢使用“宏”命令来提高办公工作效率。但在 Microsoft Office 程序中使用宏时&#xff0c;总会弹出宏安全警告&#xff0c;这让使用者倍感麻烦。而如果把宏的安全级设置为“低”&#xff0c;就可以取消excel中宏安全提示框了&#xff0c;又增加了恶意代码和病毒攻击…

关于PPT的课后作业

动手动脑问题应用的是方法重载。 课后作业代码: import java.util.*; public class ArithmeticExam { private static final Random random = new Random(); private static final Scanner scanner = new Scanner(Sys…

学校网站源码html高端网站建设的品牌

I:ASP.NET MVC3 部署的前期工作 1.确认部署的服务器操作系统环境 首先我们确认服务器的操作系统版本可以从系统命令行工具里输入: systeminfo 获取相关操作系统信息例如然后再确认IIS版本信息 -> 打开IIS管理工具即可接着确认.NET Framework的版本可以在系统命令行工具执行:…