log4cpp源码阅读:NDC类学习

1059 篇文章 285 订阅

简介

  • 位置:
    • include/log4cpp/NDC.hh
    • src/NDC.cpp
  • NDC全称是nested device context,其内部提供的方法都是线程安全的。并且这种实现安全的手段不是通过threading::mutex,而是通过threading::ThreadLocalDataHolder来实现的
  class LOG4CPP_EXPORT NDC {
	...  
 }

内部成员

它的内部成员实际上只有一个 std::vector<DiagnosticContext>一个成员,所以我们先来看一下DiagnosticContext的实现。

DiagnosticContext内部类

实质上是NDC的某些操作的基本单元。内部结构如下

    struct DiagnosticContext {
            DiagnosticContext(const std::string& message);
            DiagnosticContext(const std::string& message, 
                              const DiagnosticContext& parent);

            std::string message;  // 当前的消息
            std::string fullMessage;   // 所有的消息= 上次所有消息 + 这次设置的消息
        };
NDC::DiagnosticContext::DiagnosticContext(const std::string& message) :
        message(message),
        fullMessage(message) {
    }

    NDC::DiagnosticContext::DiagnosticContext(const std::string& message, 
            const DiagnosticContext& parent) :
        message(message),
        fullMessage(parent.fullMessage + " " + message) {
    }

从源码中可以看到,假如有如下一组对象:
在这里插入图片描述
即:fullMessage的内容总是包含前一次的内容,而message包含的是当前的内容。

ContextStack

typedef std::vector<DiagnosticContext> ContextStack;

ContextStack是NDC内部定义的一个std::vector<DiagnosticContext>的别名,它是NDC的一个主要成员的基本类型,并且使用这种类型完成了一个叫做Context栈的实现,即可以入栈push,还可以获取栈顶元素,还可以出栈。

NDC的实现

它内部包含了一个ContextStack类型的对象,还有两个只是辅助某些功能实现的标志类似的东西。这个类可以执行线程安全的将某个字符串进行入栈操作、还可以获取栈顶元素,出栈。

class NDC {
public:
    typedef std::vector<DiagnosticContext> ContextStack;    //定义的一种类型别名

    static NDC &getNDC(); //从线程局部获取当前线程的NDC对象
    ...

private:
    ContextStack _stack;    //内部的一个具体成员
};

这儿有一个特别的方法,getNDC(),这个方法是这个类的工厂方法,他还有一点特别的是,是什么呢?看一下他的源码实现:

namespace {
        threading::ThreadLocalDataHolder<NDC> _nDC; //_nDC命名为【ndcThreadLocal】更加贴切   
    }


  NDC& NDC::getNDC() {
        NDC* nDC = _nDC.get();

        if (!nDC) {
            nDC = new NDC();
            _nDC.reset(nDC);
        }

        return *nDC;
    }

这儿的代码逻辑有点像单例吗,其实不然,首先ndc指针是从线程局部中取的获取一个ndc值,因为第一次去的时候肯定为空,这个实现 就需要new一个值放入到这个线程局部中。并且将此只返回,当下次再从线程局部中取值的时候,取得就是上次设置的值。

这样就能做到每一个线程一个NDC了:

#include <iostream>
#include <thread>
#include <log4cpp/NDC.hh>
using namespace std;
using namespace log4cpp;

void thread_proc() {
    cout <<  "sub thread:" << &NDC::getNDC() << endl;
}

int main() {
    cout <<  "main thread:" << &NDC::getNDC() << endl;
    thread(thread_proc).join();
    return 0;
}

结果:

main thread: 0066CB80
sub thread:00687AB8

从而实现了线程安全。这个方法是NDC中最精彩的一个方法。其他的静态方法,其实内部都是调用获取的NDC对象的真正实现方法

入栈操作(Push)

class NDC {
public:
    static bool _isUsedNDC;

    static void push(const std::string& message);
};
bool NDC::_isUsedNDC = false;



void NDC::push(const std::string& message) {
    	if (!isUsedNDC)
    		isUsedNDC = true;
        getNDC()._push(message);
    }

push方法(入栈方法,静态方法),这儿会检测一个标志是否为false,如果为false则设置为true,然后调用的是对象真正的实现方法。这儿为什么要定义一个 _isUsedNDC成员,我们可以再后面的代码中看到。

获取栈顶元素(Get)

class NDC {
public:
	   static std::string pop();
};




    const std::string& NDC::get() {
    	if (isUsedNDC)
    		return getNDC()._get();
    	else
    		return emptyString;
    }

getNDC()方法内部调用的是 NDC::getNDC(),只要获取的话,那么它的内部就会创建一个新的NDC对象(第一次调用),现在这个NDC对象中是没有数据的,所以这次调用不就白调用了吗?而且有可能是误调用,只有有数值放进来的时候, 才会将这个标志设置为空,那么下次再调用get()方法的时候,就可以很自然的获取数据了。

出栈操作(Pop)

class NDC{
	  static std::string pop();
}


   std::string NDC::pop() {
        return getNDC()._pop();
    }

作用:会将栈顶元素内容弹出。真正的实现部分是_pop()方法

删除所有元素(Clear)


class NDC{
	  static void clear();
}

 void NDC::clear() {
        getNDC()._clear();
    }

真正的实现部分是_clear()方法

真正实现

上面的这几个静态方法都是各个方法所对应的同语义的非静态方法,源码如下:

    // 对应静态get方法的真正非静态实现
    // 出栈,取得是最后一个元素(每一个元素是DiagnosticContext类型)的fullMessage
    const std::string& NDC::_get() const {
        static std::string empty = "";

        return (_stack.empty() ? empty : _stack.back().fullMessage);
    }

  
    // 真正入栈实现部分,实现稍微需要注意,因为DiagnosticContext有两个参数,所以当第一次压栈的时候,需要做特别处理。 然后以后每次压栈,都是将栈顶元素作为新的DiagnosticContext的父对象存在的
    void NDC::_push(const std::string& message) {
        if (_stack.empty()) {
            _stack.push_back(DiagnosticContext(message));
        } else {            
            _stack.push_back(DiagnosticContext(message, _stack.back()));
        }
    }

    // 清除_stack中的所有内容非常简单
  void NDC::_clear() {
        _stack.clear();
    }


	
    std::string NDC::_pop() {
        // 出栈操作,会将栈顶元素返回(就是最后一次入栈的元素原封不动的返回)
	    if (!_stack.empty()) { // 必须首先检测一下
	        std::string result = _stack.back().message;
	        _stack.pop_back();
	        return result;
	    } else {
	        return emptyString;
	    }

    }

其实上面的入栈操作,出栈操作,获取栈顶元素,清除所有,都是对内部数据_contextStack(std::vector)的一种操作

其他方法

class NDC {
    // new的当前的_contextStack的拷贝将它返回
    static ContextStack *clone() {
        return getNDC()._cloneStack();
    }

    // 获取深度,实际上返回的就是_contextStack的size()
    static size_t getDepth() {
        return getNDC()._getDepth();
    }

    // 他的真正实现为空,因为ndc内部使用的容器是vector,他可以存放任意多的元素
    static void setMaxDepth(int maxDepth) {
        getNDC()._setMaxDepth(maxDepth);
    }

    // 实现就是将stack栈中的内容,拷贝到当前线程的_stackContext中
    // 用处:实现将父类线程中的_stackContext内容拷贝到子线程的_stackContext中
    static void inherti(ContextStack *stack) {
        return getNDC()._inhert(stack);
    }
};

    NDC::ContextStack* NDC::_cloneStack() {
        return new ContextStack(_stack);  // std::vector()的拷贝构造
    }

	size_t NDC::_getDepth() const {
        return _stack.size();   //vector.size()
    }

    void NDC::_inherit(NDC::ContextStack* stack) {
        _stack = *stack;  //当前线程的_stack 拷贝stack中的内容
    }

 	void NDC::_setMaxDepth(int maxDepth) {
        // XXX no maximum   
    }

NDC经常用的方法:

  • 静态方法push,用来将指定字符串压栈
  • 静态方法get,获取栈顶元素,返回的字符串包含所有已经压栈的字符串
  • 静态方法pop,用于出栈操作,返回的上次压入过来的字符串
  • 静态方法clear,用来删除栈中所有的元素

总结

  • NDC实现 = std::vector + 线程局部变量
  • NDC 是一个使用线程局部存储的一个不用加锁的多线程数据能够实现安全访问的技术。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值