实时多线程系统的日志实现

实时多线程系统的日志实现

2008-03-21 09:19 黄明/戴颖  软件世界 我要评论(0) 字号:T | T
一键收藏,随时查看,分享好友!

为了分析软件系统在测试和运行期产生的故障,目前大多数软件系统所广泛使用的一种方法就是日志记录。本文给出了利用循环缓冲区和单独的日志读写线程实现实时系统的日志功能。

AD:


    为了分析软件系统在测试和运行期产生的故障,目前大多数软件系统所广泛使用的一种方法就是日志记录。但系统在保存日志时需要大量的磁盘I/O操作,同时由于多线程并发互斥访问文件系统可能造成的阻塞,会引起实时软件系统的性能下降,严重时造成整个系统停止响应。本文给出了利用循环缓冲区和单独的日志读写线程实现实时系统的日志功能。

    “如果有两种方式可以编写出没有错误的程序,那么只有第三种方式是有效的。” —ALGOL语言之父Alan J. Perlis。

    任何软件系统,都不可避免的存在设计上的BUG,发现BUG和解决BUG贯穿于软件系统的整个生命期。当软件系统产生非预期的结果时,分析故障原因成为首要任务。对于非实时的单线程程序,可采取设置断点、单步跟踪等手段,能比较容易地确定故障点。然而对于实时的多线程软件系统来说,这些方法不能满足实时性要求,从而在调试过程中往往使系统变得不可用。因此日志记录成为关键的计算机应用系统的生存期中一件非常重要的活动。通过分析在系统出现故障时的日志,即可确定故障原因。在运行过程中,系统将不断地产生跟踪数据,并将其写入到磁盘上的文本文件中。通常的做法是在程序运行的关键点把系统的状态信息写入磁盘,然而这种做法的副作用就是,由于磁盘I/O操作所耗费的时间往往远大于系统中实际工作线程的执行时间,甚至一次磁盘操作消耗的时间比一个线程的整个生命期还要长,这就影响了线程的执行速度,再考虑到线程间的同步运行,其他线程可能会等待该线程的执行结果,这样就会造成整个系统性能的下降,不能满足实时系统的要求。同时由于可能不只一个线程在记录运行日志,因此会有多个线程并发的访问磁盘,造成线程间的长时间阻塞和等待,更大大影响了系统运行时的响应速度,严重时可能造成系统停止响应。这种情况在实时性要求高的系统中是不允许的。

    笔者在最近为IPSWITCH系统开发的通信网关中,利用日志线程和循环缓冲区解决了上述问题。本文把所有用来实现系统自身功能的线程统称为“工作线程”,用于读写磁盘记录日志的线程称为“日志线程”,一个可循环使用、被所有线程共享的固定大小的内存区域称为“循环缓冲区”。

    在工作线程中,在需要记录日志的地方把程序运行的状态信息写入循环缓冲区,再由单独的日志线程读出并写入磁盘保存。在工作线程把日志写入缓冲区时占用的时间很少,基本不影响工作线程运行。在日志线程进行磁盘I/O操作时,不占用工作线程的运行时间,同时由于只有一个单独的日志线程对单个日志文件进行读写,因此完全避免了由于并发操作同一文件而造成的线程间的阻塞。

    以下是实现上述功能的伪代码(根据面向对象思想设计成日志类):

    日志类说明

    Class ClassLogThread

    private:

    var

    Head//循环缓冲区写入指针

    Tail//循环缓冲区读取指针

    LogCount//缓冲区中待处理日志记数器

    Buffer[1024]//循环缓冲区(大小可根据实际情况在这里是一个字符串数组,数组中每个元素代表一行日志)

    Lock//同步变量,用于多线程同时读写循环缓冲区并发控制

    F//日志文件句柄

    procedureExecute//线程函数体,实现日志线程功能

    public:

    procedureADD(状态信息)//工作线程调用,把状态信息写入缓冲区

    日志类实现

    构造函数

    create

    begin

    getmem(Buffer)//申请缓冲区

    Head <- 0 //初始化指针

    Tail <- 0

    LogCount <- 0//初始化待处理日志记数器

    F <- Open(Filename)//创建或打开日志文件

    Createthread(logthread,execute)//创建日志线程并指定线程函数

    end

    析构函数

    destroy

    begin

    close(F)//关闭日志文件

    free(Buffer)//释放缓冲区

    terminate(logthread)//中止释放日志线程

    free(logthread)

    end

    线程函数体

    Execute

    Begin

    While not terminate do//循环直到线程被中止

    begin

    If LogCount > 0 then //缓冲区中有待处理日志

    Writeln(f,Buffer[tail])//写日志到文件

    Tail <- Tail + 1 mod sizeof(buffer)//循环移动指针

    Lock.Acquire//互斥访问

    LogCount <- LogCount-1

    Lock.Release//释放互斥资源

    Endif

    end

    end

    公有成员函数:ADD

    ADD(状态信息)//工作线程(或住线程)调用,把状态信息写入缓冲区

    Begin

    Lock.Acquire

    If LogCountBuffer[Head] <- 状态信息

    Head <- head + 1 mod sizeof(buffer)

    LogCount <- LogCount + 1

    Endif

    Lock.Release

    End

    在程序的初始化部分创建一个日志类的实例

    Log<-ClassLogThread.create

    在工作线程中把状态信息写入缓冲区

    ……

    i <- func(j)

    Log.ADD(timetostr(now) + inttostring(threadID) + “current i is ” + inttostring(i))

    ……

    在以上的代码可以看到,在工作线程中用读写内存代替磁盘I/O操作,保证了工作线程以及系统的响应速度。同时我们也注意到,工作线程把日志写入缓冲区时同样需要线程间的互斥访问,否则就会产生意想不到的后果。通常,每个工作线程在写入缓冲区之前都要锁定共享变量LogCount和Head,在完成操作时释放该资源。但由于锁定的只是共享内存变量而非磁盘资源,因此用在同步上的时间开销同样很少。为了减少锁定资源给程序带来的负面影响,在设计时应注意最小化锁定对象,尽量把无需锁定的代码放在锁定范围之外,例如线程函数Execute中判断是否有待处理日志时,并没有锁定LogCount,同时由于Tail变量和磁盘操作都是日志线程单独操作,也被放在锁定范围之外,因此不会影响其他工作线程的运行。

    一些需要注意的事项和提示:

    ◆在系统开始运行时就应分配好缓冲区,并且在线程中读写缓冲区时应注意缓冲区的边界,否则可能造成内存溢出。
    ◆在代码中可以看到,当缓冲区满时,可能会丢弃一些日志,因此为了保存所有日志,应该把缓冲区设置得尽可能大,同时精心设计需要日志记录的关键点,减少不必要的日志记录。
    ◆在日志中记录工作线程的ID,以便在日后分析时方便的提取同一线程的日志。
    ◆在日志中加时间戳,这样可方便分析不同线程之间的同步关系。
    ◆对于有大量线程同时进行日志记录的系统来说,这些线程将在获得和释放锁上花费大部分的时间,会影响整体系统的性能。这时可以创建多个日志线程类的实例,各自独立操作不同的缓冲区和文件,减少共享冲突的发生。在工作线程中,把日志分类,根据不同的日志类型,写入到不同的缓冲区,再由相应的日志线程写入不同文件。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/353813.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    Java中获取当前函数名

    Java中获取当前函数名 博客分类&#xff1a; Java JavathreadJDKIDEA 有时候我们需要在程序中获取当前运行的函数名&#xff0c;如何简单的做到这点呢&#xff1f;我们可以用getStackTrace轻松搞定。 一提到getStatckTrace多数人会联想到Thowable中的getStackTrace方法。的确&…

    建站手册-网站主机:电子商务主机

    ylbtech-建站手册-网站主机&#xff1a;电子商务主机1.返回顶部 1、http://www.w3school.com.cn/hosting/host_ecommerce.asp2、2.返回顶部1、如果您的公司正在销售某种产品或服务&#xff0c;那么电子商务也许是做生意的一种好办法。 Internet 商业 电子商务就是在 Internet 上…

    ConcurrentLinkedQueue

    ConcurrentLinkedQueue 因为ConcurrentLinkedQueue是线程序安全的并且是针对并发的 主类 Java代码 public class conn{ public static void main(String[] args) throws Exception{ Queue<String> queuenew ConcurrentLinkedQueue<String>(); …

    【大型网站技术实践】初级篇:借助Nginx搭建反向代理服务器

    每天学习一点点 编程PDF电子书、视频教程免费下载&#xff1a;http://www.shitanlife.com/code一、反向代理&#xff1a;Web服务器的“经纪人” 1.1 反向代理初印象 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受internet上的连接请求&#xff0c;…

    STM32-RS485通信软硬件实现

    OS&#xff1a;Windows 64 Development kit&#xff1a;MDK5.14 IDE&#xff1a;UV4 MCU&#xff1a;STM32F103C8T6/VET6 AD&#xff1a;Altium Designer 18.0.12 1、RS485简介  RS-485又名TIA-485-A, ANSI/TIA/EIA-485或TIA/EIA-485。RS485是一个定义平衡数字多点系统中的驱动…

    Java 枚举7常见种用法

    DK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能&#xff0c;却给我的开发带来了“大”方便。 用法一&#xff1a;常量 在JDK1.5 之前&#xff0c;我们定义常量都是&#xff1a; publicstaticfianl....。现在好了&#xff0c;有了枚举&#xff0c;可以把相关的常…

    深度学习训练数据打标签过程

    深度学习训练数据打标签过程 为了获取大量的图片训练数据&#xff0c;在采集数据的过程中常用视频的方式采集数据&#xff0c;但对于深度学习&#xff0c;训练的过程需要很多的有有标签的数据&#xff0c;这篇文章主要是解决视频文件转换成图片文件&#xff0c;并加标签&#x…

    Python中转换角度为弧度的radians()方法

    Python中转换角度为弧度的radians()方法 这篇文章主要介绍了Python中转换角度为弧度的radians()方法,是Python入门中的基础知识,需要的朋友可以参考下 radians()方法把角度转化为弧度角x。 语法 以下是radians()方法的语法&#xff1a; radians(x) 注意&#xff1a;此函数是无…

    如何招聘一个合格的程序员?

    如何招聘一个合格的程序员&#xff1f; 发表于2012-12-03 16:29| 11559次阅读| 来源TheNextWeb| 23 条评论| 作者张祺 招聘程序员摘要&#xff1a;作者是ApeForest和ContentForest网站联合创始人Pravin Daryani。他在创办网站过程中&#xff0c;学习到了非常宝贵的经验教训。如…

    JAXB和Log4j XML配置文件

    Log4j 1.x和Log4j 2.x均支持使用XML文件来指定日志记录配置 。 这篇文章探讨了与使用JAXB通过Java类处理这些XML配置文件相关的一些细微差别。 本文中的示例基于Apache Log4j 1.2.17 &#xff0c; Apache Log4j 2.6.2和Java 1.8.0_73&#xff08;带有JAXB xjc 2.2.8-b130911.18…

    (转载)浅谈线段树

    浅谈线段树 数据结构——线段树 O、引例 A.给出n个数&#xff0c;n<100&#xff0c;和m个询问&#xff0c;每次询问区间[l&#xff0c;r]的和&#xff0c;并输出。 一种回答&#xff1a;这也太简单了&#xff0c;O&#xff08;n&#xff09;枚举搜索就行了。 另一种回答&…

    双显示器设置:如何设置一台电脑两个显示器

    双显示器设置&#xff1a;如何设置一台电脑两个显示器 -来源&#xff1a;互联网 作者&#xff1a;佚名 时间&#xff1a;04-11 09:00:18 【大 中 小】 点评&#xff1a;双显示器设置,如何设置一台电脑两个显示器&#xff1a;一般来说一台电脑通常只配一个显示器&#xff0c;在我…

    结对第二次作业

    题目要求 我们在刚开始上课的时候介绍过一个小学四则运算自动生成程序的例子&#xff0c;请实现它&#xff0c;要求&#xff1a; 能够自动生成四则运算练习题可以定制题目数量用户可以选择运算符用户设置最大数&#xff08;如十以内、百以内等&#xff09;用户选择是否有括号、…

    Spy++原理初探

    Spy原理初探 http://www.vckbase.com/index.php/wv/1480.html文章概要&#xff1a;用Visual Studio搞开发的朋友对Spy这个工具一定不陌生&#xff0c;它可以分析窗体结构、进程和窗口消息&#xff0c;对开发工作有很大辅助作用。我们需要研究某个对象时&#xff0c;只要调出其…

    微信小程序的scroll-view组件

    scroll-view为滚动视图&#xff0c;共有水平滚动和垂直滚动两种使用竖向滚动时&#xff0c;需要给<scroll-view/>一个固定高度&#xff0c;通过 WXSS 设置 height。index.wxss 是页面的结构文件&#xff1a;<!--垂直滚动--> <view class"section">…

    5个常见的Hibernate异常及其解决方法

    了解如何使用Hibernate轻松解决最常见的问题 Hibernate可能是市场上最受欢迎的JPA实现&#xff0c;您可以在许多地方看到它&#xff0c;例如&#xff1a; 您自己使用过的项目数&#xff0c; 需要Hibernate经验的职位数量&#xff0c;当然还有 互联网上发布的问题和例外数量…

    MATLAB figure中提取数据

    MATLAB figure中提取数据 (2011-10-26 14:26:21) 转载▼标签&#xff1a; 杂谈 分类&#xff1a; matlab figure画出来&#xff0c;提取数据有很多好处&#xff0c;方便保存&#xff0c;计算&#xff0c;加工&#xff0c;还可以导入到origin里面画图。具体的方法就是两部。第一…

    oracle客户端中文乱码问题的解决

    1 查看服务器端编码 select userenv(language) from dual; 我实际查看到的结果为&#xff1a; USERENV(LANGUAGE) ----------------------------- AMERICAN_AMERICA.ZHS16GBK 2 执行语句 select * from V$NLS_PARAMETERS; 查看第一行PARAMETER项中为NLS_LANGUAGE对应的VALUE项…

    avi文件格式详解

    avi文件格式详解 AVI是音频视频交错(Audio Video Interleaved)的英文缩写&#xff0c;它是Microsoft公司开发的一种符合RIFF文件规范的数字音频与视频文件格式&#xff0c;原先用于Microsoft Video for Windows (简称VFW)环境&#xff0c;现在已被Windows 95/98、OS/2等多数操作…

    并发编程---线程queue---进程池线程池---异部调用(回调机制)

    线程 队列&#xff1a;先进先出堆栈&#xff1a;后进先出优先级&#xff1a;数字越小优先级越大&#xff0c;越先输出import queueq queue.Queue(3) # 先进先出-->队列q.put(first) q.put(2) # q.put(third) # q.put(4) #由于没有人取走&#xff0c;就会卡主 q.put(4,block…