在多线程应用程序中使用循环缓冲区高效地进行日志记录

在关键的计算机应用程序的生存期中,日志记录是一件非常重要的活动,特别是当故障的症状并不十分明显时。日志记录提供了故障前应用程序状态的详细信息,如变量的值、函数的返回值等等。在一段时间的运行过程中,将不断地产生大量的跟踪数据,并持续地将其写入到磁盘上的文本文件中。要进行有效的日志记录,需要使用大量的磁盘空间,并且在多线程环境中,所需的磁盘空间会成倍地增加,因为大量的线程都在记录它们的跟踪信息。

使用常规文件进行日志记录的两个主要问题是:硬盘空间的可用性,以及在对一个文件写入数据时磁盘 I/O 的速度较慢。持续地对磁盘进行写入操作可能会极大地降低程序的性能,导致其运行速度缓慢。通常,可以通过使用日志轮换策略来解决空间问题,将日志保存在几个文件中,当这些文件大小达到某个预定义的字节数时,对它们进行截断和覆盖。

要克服空间问题并实现磁盘 I/O 的最小化,某些程序可以将它们的跟踪数据记录在内存中,仅当请求时才转储些数据。这个循环的、内存中的缓冲区称为循环缓冲区。本文讨论了循环缓冲区的一些常见实现,并对多线程程序中循环缓冲区的启用机制提出了一些观点。

循环缓冲区是一种用于应用程序的日志记录技术,它可以将相关的数据保存在内存中,而不是每次都将其写入到磁盘上的文件中。在需要的时候(比如当用户请求将内存数据转储到文件中时、程序检测到一个错误时,或者由于非法的操作或者接收到的信号而引起程序崩溃时)可以将内存中的数据转储到磁盘。循环缓冲区日志记录由一个固定大小的内存缓冲区构成,进程使用这个内存缓冲区进行日志记录。顾名思义,该缓冲区采用循环的方式进行实现。当该缓冲区填满了数据时,无需为新的数据分配更多的内存,而是从缓冲区开始的位置对其进行写操作,因此将覆盖以前的内容。请参见下图的示例。

上图显示了将两个条目写入到循环缓冲区后该缓冲区的状态。在写入了第一个日志条目(用蓝色表示)之后,当该进程尝试写入第二个条目(用红色表示)时,该缓冲区中已经没有足够的剩余空间。该进程写入数据,一直到达缓冲区的末尾,然后将剩余的数据复制到缓冲区的开始位置,覆盖以前的日志条目。

通过保存一个读指针,可以实现对循环缓冲区的读操作;相应地移动读指针和写指针,以确保在进行读操作期间,读指针不会越过写指针。为了提高效率,一些应用程序可以将原始数据(而不是经过格式化的数据)保存到该缓冲区。在这种情况下需要一个解析器,该解析器可以根据这些内存转储生成有意义的日志消息。

当您可以简单地对一个文件进行写入操作时,为什么要使用循环缓冲区呢?因为您覆盖了循环缓冲区中以前的内容,所以在完成该操作后,您将丢失以前的数据。与传统的文件日志记录机制相比,循环缓冲区提供了下列优势。

  • 速度快。与磁盘的 I/O 操作相比,内存的写操作要快得多。仅当需要的时候才刷新数据。
  • 持续的日志记录可能会填满系统中的空间,从而导致其他程序也耗尽空间并且执行失败。在这样的情况下,您有两种选择,要么手动地删除日志信息,要么实现日志轮换策略。
  • 一旦您启用了日志记录,无论您是否需要它,该进程都将持续地填充硬盘上的空间。
  • 有时,您仅仅需要程序崩溃之前的相关数据,而不是该进程完整的历史数据。
  • 有一些常见的调试函数,如 printf、write 等,可能会在多线程应用程序的情况下更改一个程序的行为,使得它们难以调试。使用这些函数会导致应用程序隐藏某些平时可能表现出来的错误。这些函数都是可撤销点,并且可能导致在线程环境中产生一个该程序并不期望的挂起信号。

有时,当其他传统的日志记录方法失败时,可以使用循环缓冲区日志记录。这个部分介绍了在多线程应用程序中使用循环缓冲区启用日志记录时需要考虑的一些重要方面。

在访问一个公共的资源时,同步 始终是多线程程序不可缺少的部分,日志记录也不例外。因为每个线程都试图对全局空间进行写操作,所以必须确保它们同步地写入内存,否则消息就会遭到破坏。通常,每个线程在写入缓冲区之前都持有一个锁,在完成操作时释放该锁。您可以下载一个使用锁对内存进行写操作的循环缓冲区示例。

这种方法具有以下的缺点:如果您的应用程序中包含几个线程,并且每个线程都在进行详细地日志记录,那么该进程的整体性能将会受到影响,因为这些线程将在获得和释放锁上花费了大部分的时间。

通过使得每个线程将数据写入到它自己的内存块,就可以完全避免同步问题。当收到来自用户的转储数据的请求时,每个线程获得一个锁,并将其转储到中心位置。因为仅在将数据刷新到磁盘时获得锁,所以性能并不会受到很大的影响。在这样的情况下,您可能需要一个附加的程序对日志信息进行排序(按照线程 ID 或者时间戳的顺序),以便对其进行分析。您还可以选择仅在内存中写入消息代码,而不是完整的格式化消息,稍后使用一个外部的实用工具解析转储数据,将消息代码转换为有意义的文本。

另一种避免同步问题的方法是,分配一个很大的全局内存块,并将其划分为较小的槽位,其中每个槽位都可由一个线程用来进行日志记录。每个线程只能够读写它自己的槽位,而不是整个缓冲区。当每个线程第一次尝试写入数据时,它会尝试寻找一个空的内存槽位,并将其标记为忙碌。当线程获得了一个特定的槽位时,可以将跟踪槽位使用情况的位图中相应的位设置为 1,当该线程退出时,重新将这个位设置为 0。同时需要维护当前使用的槽位编号的全局列表,以及正在使用它的线程的线程信息。

要避免出现这样的情况,即一个线程已经死亡,但是却没有将其槽位在位图中对应的位设置为 0,您需要一个垃圾收集器线程,它遍历全局列表,并根据线程 ID 以固定的时间间隔进行轮询。它负责释放槽位并修改全局列表。

网上找的一个实现多线程环境下循环缓冲区的实现代码:

[cpp] view plaincopyprint?
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <pthread.h>
  5. #include <string.h>
  6. /*
  7. * The program reads from a text file, each of the threads takes that message
  8. * and writes it to the Ring Buffer after attaching its thread-id to it. Finally,
  9. * the Ring Buffer is dumped to the file out.txt. The command line argument is the name of
  10. * the input message file.
  11. */
  12. struct ring_buffer
  13. {
  14. char * write_pointer;
  15. char * buff_start;
  16. int wrapped;
  17. unsigned long size;
  18. };
  19. /** Function Prototypes **/
  20. void init_buffer(unsigned long);
  21. int load_data(void*, unsigned int*);
  22. void print_buffer_state();
  23. char* mem_allocate(unsigned long);
  24. int dump_buffer(const char*);
  25. /** Global Variables **/
  26. struct ring_buffer global_ring;
  27. pthread_mutex_t mutex;
  28. char file_contents[300000];
  29. /** Fucntions **/
  30. char* mem_allocate(unsigned long mem_size)
  31. {
  32. char* data;
  33. data = (char*)calloc(mem_size, sizeof(unsigned char));
  34. if(data == NULL)
  35. {
  36. perror( "calloc" );
  37. exit(1);
  38. }
  39. return data;
  40. }
  41. void init_buffer(unsigned long mem_size)
  42. {
  43. if(pthread_mutex_init(&mutex, 0) != 0)
  44. {
  45. perror("pthread_mutex_init");
  46. exit(1);
  47. }
  48. global_ring.size = mem_size;
  49. global_ring.buff_start = mem_allocate(mem_size);
  50. global_ring.write_pointer = global_ring.buff_start;
  51. global_ring.wrapped = 0;
  52. }
  53. int load_data(void* data, unsigned int* len)
  54. {
  55. int temp = 0;
  56. if(len == NULL || *len == 0)
  57. {
  58. return -1;
  59. }
  60. if(*len > global_ring.size)
  61. {
  62. *len = global_ring.size;
  63. }
  64. if(pthread_mutex_lock(&mutex) != 0)
  65. {
  66. perror("pthread_mutex_lock");
  67. exit(1);
  68. }
  69. temp = (global_ring.buff_start + global_ring.size) - global_ring.write_pointer;
  70. if(*len > temp)
  71. {
  72. memcpy(global_ring.write_pointer, data, temp);
  73. memcpy(global_ring.buff_start, (char*)data+temp, *len-temp);
  74. global_ring.write_pointer = global_ring.buff_start + *len-temp;
  75. global_ring.wrapped = 1;
  76. }
  77. else
  78. {
  79. memcpy(global_ring.write_pointer, data, *len);
  80. global_ring.write_pointer += *len;
  81. }
  82. if(pthread_mutex_unlock(&mutex) != 0)
  83. {
  84. perror("pthread_mutex_lock");
  85. exit(1);
  86. }
  87. return 0;
  88. }
  89. int dump_buffer(const char* file_to_dump)
  90. {
  91. FILE* fp ;
  92. int temp = 0;
  93. int len;
  94. if ((fp = fopen(file_to_dump, "a")) == NULL)
  95. return -1;
  96. pthread_mutex_lock(&mutex);
  97. if(global_ring.wrapped == 1)
  98. {
  99. fwrite( global_ring.buff_start, sizeof(unsigned char), global_ring.size, fp);
  100. }
  101. else
  102. {
  103. len = global_ring.write_pointer - global_ring.buff_start + 1;
  104. fwrite(global_ring.buff_start, sizeof(unsigned char), len, fp);
  105. }
  106. fclose(fp);
  107. pthread_mutex_unlock(&mutex);
  108. return 0;
  109. }
  110. void print_buffer_state()
  111. {
  112. printf("\n/***** Printing the buffer before dump****/\n");
  113. printf("Buffer data from Start pointer:\n%s\n", global_ring.buff_start);
  114. printf("/******************************/\n\n");
  115. }
  116. int read_input(char* filename)
  117. {
  118. FILE *inputFilePtr;
  119. char *iReturn;
  120. inputFilePtr = fopen(filename, "r");
  121. if(inputFilePtr == NULL)
  122. {
  123. printf("ERROR: File Not Found\n");
  124. return -1;
  125. }
  126. iReturn = fgets(file_contents, 209600, inputFilePtr);
  127. if (iReturn == NULL) /* End of file reached */
  128. return -1;
  129. return 0;
  130. }
  131. void* loader_function(void* a)
  132. {
  133. unsigned int len, current_thread, old_state = 0;
  134. char* local_msg;
  135. current_thread = pthread_self();//得到当前的线程
  136. local_msg = (char*)calloc(sizeof(file_contents)+32 , sizeof(unsigned char));
  137. if(sprintf(local_msg, "%s:%d", file_contents, current_thread) <0)
  138. {
  139. printf(":(\n");
  140. }
  141. printf("Message loaded by thread %d to the buffer = \"%s\"\n", pthread_self(), local_msg);
  142. len = strlen(local_msg);
  143. load_data(local_msg, &len);
  144. pthread_exit(0);
  145. }
  146. void threaded_test(char* filename)
  147. {
  148. pthread_t threads[2];
  149. int i,j, ret,toDump = 0;
  150. int data_read = read_input(filename);
  151. if(data_read < 0)
  152. exit(1);
  153. printf("FILE_CONTENTS = %s\n", file_contents);
  154. if(pthread_mutex_init(&mutex, 0) != 0)
  155. {
  156. perror("pthread_mutex_init");
  157. exit(1);
  158. }
  159. init_buffer(20);
  160. /***** Threads Created for to load the data *****/
  161. for(j = 0; j<2; j++)
  162. {
  163. pthread_create (&threads[j], NULL, &loader_function, NULL);
  164. }
  165. for(j = 0; j<2; j++)
  166. {
  167. pthread_join(threads[j], NULL);
  168. }
  169. print_buffer_state();
  170. dump_buffer("out.txt");
  171. printf("Buffer dumped to file \"out.txt\"\n\n");
  172. }
  173. int main(int argc, char** argv)
  174. {
  175. if(argc!=2)
  176. {
  177. printf("Usage: ./test <input-file>\n");
  178. exit(1);
  179. }
  180. else
  181. {
  182. threaded_test(argv[1]);
  183. }
  184. }
参考:http://www.ibm.com/developerworks/cn/aix/library/au-buffer/index.html#download

在多线程应用程序中使用循环缓冲区高效地进行日志记录相关推荐

  1. c语言清除键盘缓冲区函数,C语言程序中清除键盘缓冲区的方法

    第四期2006年12月益阳职业技术学院学报 JournalofYiyangVocationalandTechnicalCollegeNO.4Dec.2006 C语言程序中清除键盘缓冲区的方法 杨长虹 ...

  2. linux内核中的循环缓冲区

    Linux内核中的循环缓冲区(circular buffer)为解决某些特殊情况下的竞争问题提供了一种免锁的方法.这种特殊的情况就是当生产者和消费者都只有一个,而在其它情况下使用它也是必须要加锁的. ...

  3. 在C和C ++中创建循环缓冲区

    概述 由于嵌入式系统的资源限制,在大多数项目中都可以找到循环缓冲区数据结构. 循环缓冲区(也称为环形缓冲区)是固定大小的缓冲区,其工作方式就像内存是连续的且本质上是循环的.随着内存的生成和消耗,不需要 ...

  4. Linux在多线程应用程序中处理信号

    目录 信号上下文 从task_struct看signal 发送信号到线程 信号是Linux中非常有用的功能,它可以将通知从一个进程发送到另一个进程,并从内核发送到该进程.在某些错误情况下(访问错误的内 ...

  5. linux 循环缓冲区 源码,Linux中的循环缓冲区

    在学习到 并发和竞态 时,其中的提到了缓冲区,用于实现免锁算法,这里转载的是大神有关循环缓冲区做的一些操作. 其中源代码在最下面的附件中,有关作者的讲解感觉很清晰,很好,不过这里说一下自己的见解: 点 ...

  6. 小程序中WxParse循环解析返回的富文本数据

    WxParse循环解析 一.遇到问题 最近在做小程序,遇到这样一个需求: 将百度的Ueditor编辑器存入的文本数据显示在小程序中,需要保留之前的样式,这就用到了 WxParse插件,它能够用解析ht ...

  7. wxparse的使用php返回数组输出,微信小程序中WxParse循环解析返回的富文本数据

    WxParse循环解析 一.遇到问题 最近在做小程序,遇到这样一个需求: 将百度的Ueditor编辑器存入的文本数据显示在小程序中,需要保留之前的样式,这就用到了 WxParse插件,它能够用解析ht ...

  8. 在多线程 Python 程序中实现多目标不同缩进格式的 logging

    <本文的原始位置: http://bluegene8210.is-programmer.com/posts/21754.html> ---- 带有动态缩进格式的自定义 logging 机制 ...

  9. java uuid 线程安全_java – 在多线程应用程序中生成相同的UUID

    我使用UUID.randomUUID().toString()将一个唯一值附加到最终存储在数据库中的字符串,并对其具有唯一约束 但是因为我的应用程序是多线程的,所以执行在UUID生成的同时发生,并且最 ...

最新文章

  1. 经典不过时,回顾DeepCompression神经网络压缩
  2. Echange配置企业邮件收发策略
  3. 由歌词引发的模式思考之下篇(模拟Spring的BeanFactory)
  4. c++ abort 函数_C ++中带有示例的abort()函数
  5. 尼克的任务(洛谷-P1280)
  6. java返回datatable_(转)在JAVA实现DataTable对象(三)——DataTable对象实现
  7. 企业之经典《HSRP热备份路由协议高级配置》技术
  8. 用C语言来实现冒泡排序
  9. 解决H61、H81、B85以上的主板安装XP系统蓝屏
  10. OpenGL学习整理------着色器
  11. conda 解决An HTTP error occurred when trying to retrieve this URL.
  12. 见证历史时刻!关于朗道-西格尔零点猜想
  13. 网易发起“疾风”智造IoT联盟,深化“互联网+先进制造业”发展
  14. GitHub快速上手指南
  15. 数据库入门day06之联接查询(脑图+详解)
  16. Java实现短链接URL生成
  17. 《软件随想录-Joel on Software》书摘
  18. 100倍分析性能提升 清华冠军团队用图数据震惊世界
  19. s32k144 isystem linux,S32k144 简易 Bootloader
  20. Kafka KSQL

热门文章

  1. 机器学习中的矩阵向量求导(一) 求导定义与求导布局
  2. 【C#】WPF实现经典纸牌游戏,适合新手入门
  3. gis空间校正没反应_使用ArcGIS进行空间校正的步骤(矢量数据)
  4. 替换文件内指定字符串
  5. CSS 字符间距letter-spacing属性
  6. 记阿里UC跟cvte社招面试-----都挂了~
  7. ZOJ 3587 Marlon's String
  8. python 文件上传之---断点续传
  9. Oracle闪回恢复区(fast recovery area,FRA)
  10. 习题 8.21 用指向指针的指针的方法对n个整数排序并输出。要求将排序单独写成一个函数。n个整数在主函数中输入,最后在主函数中输出。