简介

  • 位置:

    • 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();elsereturn 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类型)的fullMessageconst 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 是一个使用线程局部存储的一个不用加锁的多线程数据能够实现安全访问的技术。

log4cpp源码阅读:NDC类学习相关推荐

  1. log4cpp源码阅读:Appender组件学习

    功能 Appender是作为Category类的观察者而存在的,它投个接收方法doAppender,是用来执行输出操作的,更确切的说,它表示要输出的地址,置于输出的格式则交给Layout组件. 我们可 ...

  2. Java源码阅读(类图自动生成工具)

    菜鸟上路,在有了基础以后,总需要去阅读大量的优秀的源码,但在面对一个项目工程大量的代码不知道怎么下手.只是跟着敲代码,我觉得这个方法不太妥当. 我是个方法论者,在收集查阅了大量的资料后发现画代码结构图 ...

  3. JVM源码阅读-Dalvik类的加载

    前言 本文主要研究Android dalvik虚拟机加载类的流程和机制.目的是了解Android中DEX文件结构,虚拟机如何从DEX文件中加载一个Java Class,以及到最终如何初始化这个类直至可 ...

  4. VTK源码阅读--vtkObject类-观察者/命令模式

    vtkObject类 vtkObject类是VTK中大多数类的基类:         vtkObject类提供了很多API用于跟踪对象修改时间,debug消息,打印类堆栈,以及事件回调:        ...

  5. sfm三维重建源码_OpenMVG源码阅读小记

    "读一份好源码,就是和许多智慧的人谈话". 本文记录了笔者学习 openMVG 开源软件的一些初步经验和心得.如果你对计算机视觉和摄影测量有兴趣,需要用到相关技术,这篇文章正好就是 ...

  6. java经典源码 阅读_公开!阿里甩出“源码阅读指南”,原来源码才是最经典的学习范例...

    我们为啥要阅读源码? 为什么面试要问源码?为什么我们Java程序员要去看源码?相信大多数程序员看到源码第一感觉都是:枯燥无味,费力不讨好!要不是为了"涨薪"我才不去看这个鬼东西!但 ...

  7. Java String类源码阅读笔记

    文章目录 一.前置 二.String类源码解析 1.String类继承关系 2.成员变量 3.构造方法 4.长度/判空 5.取字符 6.比较 7.包含 8.hashCode 9.查询索引 10.获取子 ...

  8. 【vn.py学习笔记(八)】vn.py utility、BarGenerator、ArrayManager源码阅读

    [vn.py学习笔记(八)]vn.py utility.BarGenerator.ArrayManager源码阅读 写在前面 1 工具函数 2 BarGenerator 2.1 update_tick ...

  9. 【vn.py学习笔记(五)】vn.py Base、Log、Oms、Email Engine源码阅读

    [vn.py学习笔记(五)]vn.py Base.Log.Oms.Email Engine源码阅读 写在前面 1 BaseEngine 2 LogEngine 3 OmsEngine 3.1 构造函数 ...

  10. Java源码阅读学习后的浅析和感悟(JDK篇)(持续更新)

    目录 Java源码阅读学习后的浅析和感悟(JKD篇) - 为什么阅读源码 集合框架类 - 为什么会要引入集合 - 集合结构图(部分) ArrayList集合源码分析 - 扩容机制 - 关键方法解释(D ...

最新文章

  1. [译]Kinect for Windows SDK开发入门(九):骨骼追踪进阶 下
  2. 【DIY】200521近期在做的项目小结,DIY进展汇报
  3. 【NLP傻瓜式教程】手把手带你HAN文本分类(附代码)
  4. apache ab压力测试学习
  5. java中套接字,如何在java中获得一个开放的套接字?
  6. A+B and C (64bit)
  7. MySQL有sqldependency_SqlDependency的局限性是什么
  8. 如何更优雅的写for循环
  9. 研究生们都在推荐哪些好用的论文在线翻译软件?
  10. html格式转换wps表格,用WPS Office轻松实现教案格式转换
  11. java version什么意思_输入java -version命令后提示结果如下,是什么意思??哪位能看懂,在线等。。。。...
  12. 机器人视觉分析算法_机器视觉处理:目标检测和跟踪
  13. Ehcache基本使用
  14. java不是关键字_以下( )不是Java的关键字。_学小易找答案
  15. 异构计算(CPU + GPU)编程简介
  16. 数据库连接池-c3p0
  17. Python中私有变量和私有方法芳
  18. wps中将文档输出为pdf_如何将wps文档转换为pdf格式
  19. 华为7c手机怎么恢复出厂设置_华为手机恢复出厂设置在哪里。
  20. RESTful API http 动词含义

热门文章

  1. 如何让linux终端不显示路径
  2. 于永正《杨氏之子》第二课时课堂实录
  3. Jmeter原件的作用域
  4. 输入用户名,判断用户名是否合法,用户名的要求:必须由数字**和**字母且只能有数字和字母,并且第一个字符是大写字母
  5. Mac上如何把图片中的文字转换成word/pdf文字
  6. 微信智慧支付日系统服务器崩溃,如何看待 10 月 29 日微信支付疑似发生崩溃?什么原因导致的...
  7. 最近读书了吗?林曦老师与你分享来自暄桐课堂的读书方法
  8. BET测试案例分享及问题解答
  9. CSS3 盾牌组合动画
  10. 2006百度之星程序设计大赛试题-变态比赛规则(解答)