基于单例模式的简单日志工具

1. 什么叫单例模式:

简单的说,就是通过这种方法,可以控制某个类型的对象在程序的整个生命周期中只有一个实例。

2. 单例模式的应用场景:

在某些应用场景下,某个对象在整个程序的生命周期只需要一个就OK。比如线程池、缓存、日志对象等。
在不使用单例的时候,比如大家约定就用某个全局变量,也能达到类似的效果。但是有个缺点,比如说,全局对象在程序一开始就要初始化,若这个全局对象很耗资源,但是在这次运行过程中没有使用到这个对象,那么资源不就浪费了吗?
我们可以通过单例模式在需要对象的时候再去创建它,而不是在程序一开始就创建好(懒汉模式)

 

3. 单例模式的实现:

3.1 如何控制对象只有一个?
程序员可以创建一个对象,就可以创建多个对象,如果要保证对象只有一个,显然,外部是不能创建对象的,所以,构造函数不能为PUBLIC。若构造器是私有的,那么外部显然就不能够创建该类了。当我们需要对象时,可以如下使用:
 Log* pLog = Log::getInstance(); 通过该方法去获取对象实例
注:getInstance方法为静态方法: static Log* getInstance();
private:
	Log(void);  //构造函数为私有

Log* Log::getInstance()
{
     if (!m_pLog)
     {
	m_pLog = new Log();
     }

     return m_pLog;
}

通过上述代码,我们从逻辑上会觉得只会创建一个Log对象。但是在多线程下会有问题。


3.2 多线程互斥问题
在如下逻辑下:上述代码会有问题:
有两个线程, 线程A, 线程B
时间片1: A 判断m_pLog 是否为NULL, 结果为TRUE,进入if语句内部
时间片2: B抢占CPU, 也判断 m_pLog  是否为NULL, 结果为TRUE,进入if语句内部
时间片3:   A 抢占CPU, 创建对象
时间片4: B抢占CPU, 创建对象

明明我们只想要一个对象,但是出现了两个,这不是我们想要的。我们只需要一个。

于是想到了用锁。将代码改成如下
添加锁变量:static Mutex m_mutex;
Log* Log::getInstance()
{
	if (!m_pLog)
	{
	   MutexLock mutexLock(&m_mutex);
           m_pLog = new Log();		
	}

	return m_pLog;
}


这个能够避免上述问题吗?只要两个线程都进入IF语句内部,加锁并不能解决会创建多个对象的问题。
这里用到一个方法就是:双重检查。
Log* Log::getInstance()
{
	if (!m_pLog)  //(1)
	{
		MutexLock mutexLock(&m_mutex); //(2)

		if (!m_pLog)  //(3)
		{
			m_pLog = new Log();  //(4)
		}
	}

	return m_pLog;
}


在上述代码中,我们在上锁之后多加了一重判断: 在按照两个线程的逻辑走一遍:

时间片1:线程 A 进入 getInstance() 方法。 

时间片2:由于(1)处 m_pLog 为 null,线程 A进入IF语句,并在(2)处 lock 

时间片3:线程 B 抢占CPU。

时间片4:线程 B进入 getInstance() 方法。

时间片5:m_pLog 仍旧为 null,线程 B 试图获取锁。然而,由于线程 A持有该锁,线程B在(2)处阻塞。

时间片6:线程 A 抢占CPU。

时间片7:线程 A 执行,由于在(3)处判断m_pLog仍为 null,线程 A 在(4)处创建一个 Log对象并让 m_pLog指向该对象。

时间片8:线程 A 解锁并从 getInstance() 方法返回实例。 

时间片9:线程 B 抢占CPU。

时间片10:线程 B 获取锁并在(3)处检查m_pLog 是否为 null。 

时间片11:由于 m_pLog 是非 null 的,并没有创建第二个 Log对象,返回由线程A创建的Log对象。

这种方法只有在对象还没有创建的时候需要加锁,等创建第一次创建好了之后,就直接返回对象指针,并不会涉及锁的操作。

 

4. 使用单例模式实现的日志工具的实现源码:

先交代一下为什么会去做这个日志工具:
在测试时,如果是明显的BUG,我们通过VS工具,比如程序崩掉,我们可以通过中断,去查看堆栈情况,确认错误错在哪里,但是有些错误并不会如此容易的定位,也不报错,但是不符合逻辑,就需要我们通过日志工具去记录关键操作及相关参数,去分析可能存在的问题。

.h文件
#pragma once

#include <string>
#include "Mutex.h"
using namespace  std;

class Log
{
private:
	Log(void);

public:
	~Log(void);
	static Log* getInstance();


public:
	void logToFile(string strFileName, int mode, char* szFormat, ...); //将日志信息输出到FILE
	void logToScreen(char* szFormat, ...);  //将日志信息输出到控制台

private:
	static Log* m_pLog;
	static Mutex m_mutex;
};

.cpp文件
#include "StdAfx.h"
#include "Log.h"
#include "DebugUtil.h"
#include <fstream>
#include "MacroDef.h"
#include <iostream>
using namespace std;

Log* Log::m_pLog = NULL;
Mutex Log::m_mutex;
Log::Log(void)
{
}

Log::~Log(void)
{
}


/************************************************************************
函数名:logToFile:
参数:  [in] strFileName 输出的文件路径名
        [in]  mode 文件打开方式
       [in]  szFormat 格式内容
功能:把内容输出到文件中
/************************************************************************/
void Log::logToFile(string strFileName, int mode, char* szFormat, ...)
{
	//LOG内容数组
	char szLogContent[LOG_CONTENT_LEN];
	memset(szLogContent, 0, LOG_CONTENT_LEN);
	
	//时间戳存放数组
	char szTime[100];
	DebugUtil::GetTimeStamp(szTime, 100);
	int nTimeLen = strlen(szTime);

	int nLogCount = 0;
	va_list pArgList;

	va_start(pArgList, szFormat);
	nLogCount = _vsnprintf(szLogContent, LOG_CONTENT_LEN, szFormat, pArgList);
	va_end(pArgList);

	fstream fileStream;
	mode = mode | ios::app | ios::out;
	
	MutexLock mutexLock(&m_mutex);
	fileStream.open(strFileName.c_str(), mode);
	fileStream.write(szTime, nTimeLen);
	fileStream.write(szLogContent, nLogCount);

	char cChangeLine = '\n';
	fileStream.write(&cChangeLine, CHAR_SIZE);
	fileStream.close();
};

void Log::logToScreen(char* szFormat, ...)
{
	//LOG内容数组
	char szLogContent[LOG_CONTENT_LEN];
	memset(szLogContent, 0, LOG_CONTENT_LEN);
	
	//时间戳内容存放数组
	char szTime[100];
	DebugUtil::GetTimeStamp(szTime, 100);

	int nListCount = 0;
	va_list pArgList;

	va_start(pArgList, szFormat);
	nListCount = _vsnprintf(szLogContent, LOG_CONTENT_LEN, szFormat, pArgList);
	va_end(pArgList);

	MutexLock mutexLock(&m_mutex);
	cout<<szTime<<szLogContent<<endl;
};

Log* Log::getInstance()  //获取LOG实例对象
{
	if (!m_pLog)
	{
		MutexLock mutexLock(&m_mutex);

		if (!m_pLog)
		{
		    m_pLog = new Log();
		}
	}

	return m_pLog;
}


注:
(1)本身单例模式有些陷阱,比如说在早起的JAVA中,双重检测有时候也是不行的。后面会讲述。
 (2)在该Log类型中,使用了变参,后面也会讲一讲。

转载于:https://my.oschina.net/myspaceNUAA/blog/55686

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值