微网站建设企划书制作网站心得
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/Loggithub.com/FengJungle/Log
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/931575.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!