SigmaTera

小鱼儿的技术池塘
随笔 - 7, 文章 - 3, 评论 - 1, 引用 - 0
数据加载中……

2009年4月27日

ACE程序员教程-第1章 ACE自适配通信环境

 

ACE程序员教程-第1章 ACE自适配通信环境

 

ACE自适配通信环境(Adaptive Communication Environment)是面向对象的构架和工具包,它为通信软件实现了核心的并发和分布式模式。ACE包含的多种组件可以帮助通信软件的开发获得更好的灵活性、效率、可*性和可移植性。ACE中的组件可用于以下几种目的:

 

 

·       并发和同步

·       进程间通信(IPC)

·       内存管理

·       定时器

·       信号

·       文件系统管理

·       线程管理

·       事件多路分离和处理器分派

·       连接建立和服务初始化

·       软件的静态和动态配置、重配置

·       分层协议构建和流式构架

·       分布式通信服务:名字、日志、时间同步、事件路由和网络锁定,等等。

 

 

 

1.1 ACE体系结构

 

  如图1-1所示,ACE具有分层的体系结构。在ACE构架中有三个基本层次:

 

 

·       操作系统(OS)适配层

·       C++包装层

·       构架和模式层

 

 

  

 

图1-1 ACE的体系结构

 

1.1.1 OS适配层

 

  OS适配层是位于本地OS API和ACE之间的“瘦”代码层,它使ACE的较高层与平台依赖性屏蔽开来,从而使得通过ACE编写的代码保持了相对的平台无关性。只需要极少的努力,开发者就可以将ACE应用移植到任何平台上。

 

  OS适配层也是ACE构架之所以可用于如此多的平台的原因所在。目前ACE适用的OS平台包括:实时OS(VxWorks、Chorus、LynxOS和pSoS)、大多数版本的UNIX(SunOS 4.x和5.x; SGI IRIX 5.x和6.x; HP-UX 9.x, 10.x和11.x; DEC UNIX 3.x和4.x; AIX 3.x和4.x; DG/UX; Linux; SCO; UnixWare; NetBSD和FreeBSD)、Win32(使用MSVC++和Borland C++的WinNT 3.5.x、4.x、Win95和WinCE)以及MVS OpenEdition。

 

1.1.2 C++包装层

 

  C++包装层包括一些C++包装类,它们可用于构建高度可移植的和类型安全的C++应用。这是ACE工具包最大的一部分,大约包含了总源码的50%。C++包装类可用于:

 

 

·       并发和同步:ACE提供若干并发和同步包装类,对本地OS多线程和多进程API进行了抽象。这些包装类封装用于线程和进程的原语,比如信号量、锁、栅栏(Barrier)和条件变量。另外还有更高级的原语可用,比如守卫(Guard)。所有这些原语共享类似的接口,因而很容易使用和相互替换。

·       IPC:ACE提供若干C++包装类,封装不同OS中不同的进程间通信(IPC)接口。例如,ACE的包装类封装了以下IPC机制:BSD socket、TLI、UNIX FIFO、流管道、Win32命名管道,等等。ACE还为消息队列提供包装类,包括特定的实时OS的消息队列。

·       内存管理组件:ACE包含的一些类可用于内存动态分配和释放;其中包括允许预分配所有动态内存的类。这些预分配的内存随即通过ACE提供的管理类的帮助进行本地管理。在大多数实时和嵌入式系统中,这样的细粒度管理极为必要。另外还有一些类用于灵活地管理进程间共享内存。

·       定时器类:有多种不同的类可用于处理定时器的调度和取消。ACE中不同种类的定时器使用不同的底层机制(堆、定时器轮(timer wheel)或简单列表)来提供不同的性能特性。但是,不管底层使用何种机制,这些类的接口都是一致的,从而使得开发者很容易使用任何一种定时器类。除了这些定时器类,还有封装高分辨率定时器(在部分平台上可用,比如VxWorks, Win32/Pentium, AIX和Solaris)和Profile Timer的包装类。

·       容器类:ACE还拥有若干可移植的STL风格的容器类,比如Map、Hash_Map、Set、List,等等

·       信号处理:ACE提供对特定OS的信号处理接口进行封装的包装类。这些类使得开发者能够很容易地安装和移除信号处理器,并且可以为一个信号安装若干处理器。另外还有信号守卫类,可用于在看守的作用域之内禁止所有信号。

·       文件系统组件:ACE含有包装文件系统API的类。这些类包括文件I/O、异步文件I/O、文件加锁、文件流、文件连接包装,等等。

·       线程管理:ACE提供包装类来创建和管理线程。这些包装还封装了针对特定OS的线程API,可被用于提供像线程专有存储这样的功能。

 

 

 

1.1.3 ACE构架组件

 

  ACE构架组件是ACE中最高级的“积木”,它们的基础是若干针对特定通信软件领域的设计模式。设计者可以使用这些构架组件来帮助自己在高得多的层面上思考和构建系统。这些组件实际上为将要构建的系统提供了“袖珍体系结构”,因此这些组件不仅在开发的实现阶段、同时在设计阶段都是有用的。ACE的这一层含有以下一些大型组件:

 

 

·       事件处理:大多数通信软件都含有大量处理各种类型事件(比如,基于I/O、基于定时器、基于信号和基于同步的事件)的代码。软件必须高效地多路分离、分派和处理这些事件。遗憾的是,大多数时间开发者们都在反复地编写这些代码,“重新发明轮子”。这是因为,事件多路分离、分派和处理代码全都紧密地耦合在一起,无法彼此独立地使用。ACE提供了被称为Reactor(反应堆)的构架组件来解决这一问题。反应堆提供用于高效地进行事件多路分离和分派的代码,并极大地降低了它们与处理代码之间的耦合,从而改善了可复用性和灵活性。

·       连接或服务初始化组件:ACE提供Connector(连接器)和Acceptor(接受器)组件,用于降低连接初始化与连接建立后应用执行的实际服务之间的耦合。在接受大量连接请求的应用服务器中,该组件十分有用。连接首先以应用特有的方式初始化,然后每一连接被相应的处理例程分别处理。这样的去耦合使得开发者能够分别去考虑连接的处理和初始化。因此,如果在随后的阶段开发者发现连接请求数多于或是少于估算,它可以选择使用不同的初始化策略集(ACE提供了若干可供开发者挑选和选择的策略),以获得所要求的性能水平。

·       流组件:ACEStream组件用于简化那些本质上是分层的(layered)或层次的(hierarchic)软件的开发。用户级协议栈的开发是一个好例子;这样的栈由若干互连的层次组成。这些层次或多或少可以相互独立地进行开发。当“数据”通过时,每一层都处理并改变它,并将它传递给下一层,以作进一步的处理。因为各层是相互独立的,它们很容易被复用或替换。

·       服务配置组件:通信软件开发者面临的另一个问题是,很多时候,软件服务必须在安装时配置,或必须在运行时重配置。应用中特定服务的实现可能需要进行改动,因而应用可能必须用新改动的服务重新配置。ACEService Configurator(服务配置器)为应用的服务提供动态的启动、挂起和配置。

 

 

 

  尽管计算机网络领域发展迅速,编写通信软件已经变得更为困难。大量消耗在开发通信软件上的努力不过是“重新发明轮子”的变种,已知的可以在应用间通用的组件被重写,而不是被复用。通过收集通用的组件和体系结构(它们在网络和系统编程领域一再被复用),ACE为这一问题提供了解决方案。应用开发者可以采用ACE,挑选和选择在他的应用中所需的组件,并开始在ACE工具箱的陪伴下构建应用。除了在C++包装层中收集简单的“积木”,ACE还包括了大的体系结构“积木”,它们采用了已被证明在软件开发领域中行之有效的“模式”和“软件体系结构”。

 

posted @ 2009-04-27 15:13 Python 阅读(1245) | 评论 (0)编辑 收藏

2009年3月20日

[转]红黑牌游戏

 

 

有这么一个游戏,叫做红黑牌游戏。游戏是两组人参加,大家手里拿有红黑两种牌,按出牌来计算得分。

    规则是:双方出牌次数都为6次,第1、2、4、5的计分为一倍,第3为两倍,第6次为3倍。如果双方一样出黑牌,双方都加3分;如果双方都出红牌,各减6分;如果一方出红牌,一方出黑牌,出红牌的加6分,出黑牌的减6分。双方比赛,正分多的为胜者。

    规则没有规定双方不可以事先进行商量。

    A、B两组人开始玩这个游戏。双方经过研究讨论,说好都出黑牌。但第一轮出牌时,A方变卦出了红牌。于是A方6分,B方负6分。

    B方很生气。在第二轮,B方马上换成红牌,A方也继续出红牌,于是双方都减分。

    下来的几轮可以想像,双方都是出红牌。B方一直在想如果A方能够出一次黑牌,大家平分,下来的几轮也许还可以达成共赢。但A方一直没有。A方在想,如果出黑牌,如果B方报复,在第6轮换成红牌,自己就要输。于是双方最后都是负分,没有胜者。

    这个游戏是说商场上的竞争,如果双方能达共赢,但互相不信任,互相恶性竞争,结果大家可能两败俱伤。游戏很简单,但意义很深刻。

    引伸到人与人之间的关系,有时候如果总是计较一时的得失,总是出红牌,最后都不敢再出黑牌,最终交不到真心朋友。

    如果我们多出黑牌,也许会遇到总出红牌的人,会吃很多的亏。但多出黑牌的人,信誉一定好,人缘一定好,让人觉得值得信任,相信他会有一帮真正的朋友。

    大智若愚有时就是这样。


posted @ 2009-03-20 16:13 Python 阅读(1703) | 评论 (1)编辑 收藏

_beginthread还是CreateThread[转]

  _beginthread还是CreateThread[转]

程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程,一直有所争论。本文将从对CRT源代码出发探讨这个问题。

I. 起因

今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread,并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解,为了对朋友负责,专门翻阅了一下VC的运行库(CRT)源代码,终于找到了答案。

II. CRT

CRT(C/C++ Runtime Library)是支持C/C++运行的一系列函数和代码的总称。虽然没有一个很精确的定义,但是可以知道,你的main就是它负责调用的,你平时调用的诸如strlen、strtok、time、atoi之类的函数也是它提供的。我们以Microsoft Visual.NET 2003中所附带的CRT为例。假设你的.NET 2003安装在C:Program FilesMicrosoft Visual Studio .NET 2003中,那么CRT的源代码就在C:Program FilesMicrosoft Visual Studio .NET 2003Vc7crtsrc中。既然有了这些实现的源代码,我们就可以找到一切解释了。

III. _beginthread/_endthread

这个函数究竟做了什么呢?它的代码在thread.c中。阅读代码,可以看到它最终也是通过CreateThread来创建线程的,主要区别在于,它先分配了一个_tiddata,并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数_threadstart中,在那里会把这个指针作为一个TLS(Thread Local Storage)保存起来。然后_threadstart会调用我们传入的线程函数,并且在那个函数退出后调用_endthread。这里也可以看到,_threadstart用一个__try/__except块把我们的函数包了起来,并且在发生异常的时候,调用exit退出。(_threadstart和endthread的代码都在thread.c中)
这个_tiddata是一个什么样的结构呢?它在mtdll.h中定义,它的成员被很多CRT函数所用到,譬如int _terrno,这是这个线程中的错误标志;char* _token,strtok以来这个变量记录跨函数调用的信息,...。
那么_endthread又做了些什么呢?除了调用浮点的清除代码以外,它还调用了_freeptd来释放和这个线程相关的tiddata。也就是说,在_beginthread里面分配的这块内存,以及在线程运行过程中其它CRT函数中分配并且记录在这个内存结构中的内存,在这里被释放了。
通过上面的代码,我们可以看到,如果我使用_beginthread函数创建了线程,它会为我创建好CRT函数需要的一切,并且最后无需我操心,就可以把清除工作做得很好,可能唯一需要注意的就是,如果需要提前终止线程,最好是调用_endthread或者是返回,而不要调用ExitThread,因为这可能造成内存释放不完全。同时我们也可以看出,如果我们用CreateThread函数创建了线程,并且不对C运行库进行调用(包括任何间接调用),就不必担心什么问题了。

IV. CreateThread和CRT

或许有人会说,我用CreateThread创建线程以后,我也调用了C运行库函数,并且也使用ExitThread退出了,可是我的程序运行得好好的,既没有因为CRT没有初始化而崩溃,也没有因为忘记调用_endthread而发生内存泄漏,这是为什么呢,让我们继续我们的CRT之旅。
假设我用CreateThread创建了一个线程,我调用strtok函数来进行字符串处理,这个函数肯定是需要某些额外的运行时支持的。strtok的源代码在strtok.c中。从代码可见,在多线程情况下,strtok的第一句有效代码就是_ptiddata ptd = _getptd(),它通过这个来获得当前的ptd。可是我们并没有通过_beginthread来创建ptd,那么一定是_getptd捣鬼了。打开tidtable.c,可以看到_getptd的实现,果然,它先尝试获得当前的ptd,如果不能,就重新创建一个,因此,后续的CRT调用就安全了。可是这块ptd最终又是谁释放的呢?打开dllcrt0.c,可以看到一个DllMain函数。在VC中,CRT既可以作为一个动态链接库和主程序链接,也可以作为一个静态库和主程序链接,这个在Project Setting->Code Generations里面可以选。当CRT作为DLL链接到主程序时,DllMain就是CRT DLL的入口。Windows的DllMain可以由四种原因调用:Process Attach/Process Detach/Thread Attach/Thread Detach,最后一个,也就是当线程函数退出后但是线程还没有销毁前,会在这个线程的上下文中用Thread Detach调用DllMain,这里,CRT做了一个_freeptd(NULL),也就是说,如果有ptd,就free掉。所以说,恰巧没有发生内存泄漏是因为你用的是动态链接的CRT。
于是我们得出了一个更精确的结论,如果我没有使用那些会使用_getptd的CRT函数,使用CreateThread就是安全的。

V. 使用ptd的函数

那么,究竟那些函数使用了_getptd呢?很多!在CRT目录下搜索_getptd,你会发觉很多意想不到的函数都用到了它,除了strtok、rand这类需要保持状态的,还有所有的字符串相关函数,因为它们要用到ptd中的locale信息;所有的mbcs函数,因为它们要用到ptd中的mbcs信息,...。

VI. 测试代码

下面是一段测试代码(leaker中用到了atoi,它需要ptd):

#include <windows.h>
#include <process.h>
#include <iostream>
#include <CRTDBG.H>

volatile bool threadStarted = false;

void leaker()
{
std::cout << atoi( "0" ) << std::endl;
}

DWORD __stdcall CreateThreadFunc( LPVOID )
{
leaker();
threadStarted = false;
return 0;
}

DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID )
{
leaker();
threadStarted = false;
_endthread();
return 0;
}

void __cdecl beginThreadFunc( LPVOID )
{
leaker();
threadStarted = false;
}

int main()
{
for(;;)
{
while( threadStarted )
Sleep( 5 );
threadStarted = true;
// _beginthread( beginThreadFunc, 0, 0 );//1
CreateThread( NULL, 0, CreateThreadFunc, 0, 0, 0 );//2
// CreateThread( NULL, 0, CreateThreadFuncWithEndThread, 0, 0, 0 );//3
}
return 0;
}

如果你用VC的多线程+静态链接CRT选项去编译这个程序,并且尝试打开1、2、3之中的一行,你会发觉只有2打开的情况下,程序才会发生内存泄漏(可以在Task Manager里面明显的观察到)。3之所以不会出现内存泄漏是因为主动调用了_endthread。

VII. 总结

如果你使用了DLL方式链接的CRT库,或者你只是一次性创建少量的线程,那么你或许可以采取鸵鸟策略,忽视这个问题。上面一节代码中第3种方法基于对CRT库的了解,但是并不保证这是一个好的方法,因为每一个版本的VC的CRT可能都会有些改变。看来,除非你的头脑清晰到可以记住这一切,或者你可以不厌其烦的每调用一个C函数都查一下CRT代码,否则总是使用_beginthread(或者它的兄弟_beginthreadex)是一个不错的选择。

 

网友condor指出本文的一个错误:在dllcrt0.c中,DllMain的Thread Detach所释放的ptd,其实是dllcrt0.c的DllMain中的Thread Attach所创建的。也就是说,当你用CRT DLL的时候,DllMain对线程做了一切初始化/清除工作。我查看源代码,thread.c中的_threadstart函数,在设置TLS之前做了检查,这其实就是为了避免重复设置导致的内存泄漏。

·                                       

 

posted @ 2009-03-20 09:29 Python 阅读(338) | 评论 (0)编辑 收藏

2009年3月18日

[转]如何理解C run-time library (C运行时库) han012(原作)

注:    以下内容部分引自CSND中相关讨论的帖子,并结合自己的理解整理而成。仅供参考。   
    
   1)运行时库就是    C    run-time    library,是    C    而非    C++   语言世界的概念:取这个名字就是因为你的    C    程序运行时需要这些库中的函数.   
    
   2)C    语言是所谓的“小内核”语言,就其语言本身来说很小(不多的关键字,程序流程控制,数据类型等);所以,C    语言内核开发出来之后,Dennis    Ritchie    和    Brian    Kernighan    就用    C    本身重写了    90%    以上的    UNIX    系统函数,并且把其中最常用的部分独立出来,形成头文件和对应的    LIBRARY,C    run-time    library    就是这样形成的。   
    
   3)随后,随着    C    语言的流行,各个    C    编译器的生产商/个体/团体都遵循老的传统,在不同平台上都有相对应的    Standard    Library,但大部分实现都是与各个平台有关的。由于各个    C    编译器对    C    的支持和理解有很多分歧和微妙的差别,所以就有了    ANSI    C;ANSI    C    (主观意图上)详细的规定了    C    语言各个要素的具体含义和编译器实现要求,引进了新的函数声明方式,同时订立了    Standard    Library    的标准形式。所以C运行时库由编译器生产商提供。至于由其他厂商/个人/团体提供的头文件和库函数,应当称为第三方    C    运行库(Third    party    C    run-time    libraries)。   
    
   4)C    run-time    library里面含有初始化代码,还有错误处理代码(例如divide    by    zero处理)。你写的程序可以没有math库,程序照样运行,只是不能处理复杂的数学运算,不过如果没有了C    run-time库,main()就不会被调用,exit()也不能被响应。因为C    run-time    library包含了C程序运行的最基本和最常用的函数。   
    
   5)到了    C++    世界里,有另外一个概念:Standard    C++    Library,它包括了上面所说的    C    run-time    library    和    STL。包含    C    run-time    library    的原因很明显,C++    是    C    的超集,没有理由再重新来一个    C++    run-time    library.    VC针对C++    加入的Standard    C++    Library主要包括:LIBCP.LIB,    LIBCPMT.LIB和    MSVCPRT.LIB   
    
   6)Windows环境下,VC提供的    C    run-time    library又分为动态运行时库和静态运行时库。   
   动态运行时库主要是DLL库文件msvcrt.dll(or    MSVCRTD.DLL    for    debug    build),对应的Import    library文件是MSVCRT.LIB(MSVCRTD.LIB    for    debug    build)   
   静态运行时库(release版)对应的主要文件是:   
   LIBC.LIB    (Single    thread    static    library,    retail    version)   
   LIBCMT.LIB    (Multithread    static    library,    retail    version)   
    
   msvcrt.dll提供几千个C函数,即使是像printf这么低级的函数都在msvcrt.dll里。其实你的程序运行时,很大一部分时间时在这些运行库里运行。在你的程序(release版)被编译时,VC会根据你的编译选项(单线程、多线程或DLL)自动将相应的运行时库文件(libc.lib,libcmt.lib或Import    library    msvcrt.lib)链接进来。   
    
   编译时到底哪个C    run-time    library联入你的程序取决于编译选项:   
   /MD,    /ML,    /MT,    /LD        (Use    Run-Time    Library)   
   你可以VC中通过以下方法设置选择哪个C    run-time    library联入你的程序:   
   To    find    these    options    in    the    development    environment,    click    Settings    on    the    Project    menu.    Then    click    the    C/C++    tab,    and    click    Code    Generation    in    the    Category    box.    See    the    Use    Run-Time    Library    drop-down    box.   
    
   从程序可移植性考虑,如果两函数都可完成一种功能,选运行时库函数好,因为各个    C    编译器的生产商对标准C    Run-time    library提供了统一的支持.   

posted @ 2009-03-18 10:32 Python 阅读(241) | 评论 (0)编辑 收藏

2009年3月15日

[转载]如何从技术专家提升为合格的项目经理

作者:熙菱信息 项目经理 尹志坚

 

说到项目管理,我管理的项目不算多,大项目也有那么两三个,小项目也就记不清了。看了别的老兄写的博客想法也有一些,只是写出来一方面能够给自己的项目有个总结,另一方面也能给新人一些建议。
我做过集成的项目,说实在话没有什么可写的,但软件的项目给我的磨砺可能是这一辈子也抹不去的。
我这人天生喜欢有一定挑战的事。所以从集成转到软件费了不少力气,从一个IT公司的集成部门经理下来做程序员一步一步的又做到软件部门经理,这个区间可能用了一年半的时间。言规正转。
说软件给我比较深刻的影响是因为软件是在创造,不说全部吧,至少大部分。
创造的东西对每个来说都是比较有意思的一件事,而且成就感也是比较强的一件事,同时对个体的打击也是比较多的,更多的时候做软件项目的项目经理让人疲惫不堪。
就我所做项目管理的的几年中总结经验如下:

项目管理第一位,技术要放第二位

    年轻的项目经理,如果你想做好一个项目经理而不是一个技术经理那么你就远记住,项目管理是第一位的工作,技术要放到第二位,一个项目的成败不是你的技术用的是多少的先进,你的功能是多么的完善,项目的好与不好只有一个人可能说,那就是你的客户和用户,即使你的项目用的最差的ASP写了一个比较大的应用,如果用户说你好,那么谁都没有理由说你差。

沟通交流放第一位,协调处理放在第二位

    在项目管理中首要的一个工作就是沟通与交流,要与你的用户沟通交流,要与你的客户沟通交流,要与你的公司各部门的相关人员沟通交流,要与你的领导沟通交流,要与你的组员沟通交流,首先要清楚,沟通不是要你去协调,是要你知道他们想要去做什么,要做到什么程度,这中间可能有什么问题,是要你先要学会听别人说,然后再考虑你的对策,项目的核心就是你,你要把各方的真实的意见汇总,然后再决定应该怎么去做,这一点很重要。你很重要的一个工作就是听别人说然后拿出自己的想法去解决。

发挥每个项目成员的作用

    一个好汉三个帮,任凭你个人能力再好,也有你顾及不到的时候,所以项目组里一定要有能够支持你和积极配合你的“哥们”,在更多关键的时候他们给你的提醒会让你避免很多错误及能够及时的帮你解决很多难题。同时要认真的对待每一个成员给你的建议,即使是很蹩脚的,因为每个人提出来的建议总是有他提出来的理由。

协调项目各个骨干成员的利益
    项目经理一方面要对用户及公司负责同时也不要忘记了和你一起出生入死的战友,项目组成员的利益也需要你来维护,这个利益来自两方面一方面要协调各个骨干成员间的利益,另一方面要协调组员与公司间的利益。

以身作则起到表率

    项目组建初期,所有的的成员大家都认为项目经理是主心骨,所以无论大小的问题都会找项目经理,并且大家也都关注着你的一言一行,所以项目经理要勇于承担责任,遇事退缩的项目经理会让组员看不起的,同时也失去了威信,发展到最后你会发现已经指挥不动人了。同时要学会合理的把相应的工作分配到不同的组员身上,不然你会发现你的工作应接不暇而部分组员在看小说聊天。所以这些问题都需要责任心来解决,当你用心去安排工作时这些工作会迎刃而解,所以项目经理的责任心是非常重要的。

不断学习不断尝试

    在国内的项目经理大部分应该都是出自技术岗位,所以还是要不断学习新技术同时安排解决项目中的技术问题,同时还要加强项目管理的知识及不断丰富自己的管理经验,一方面加强技术学习不但可以把控项目中的技术问题同时可以得到组员的认同,教材上项目管理知识都是理论的,根据不同的环境和情况要不断尝试用新的方式来解决不同的问题。

你的主要工作是管理

    正因为很多软件项目的项目经理都是出自技术,所以不同程度的对技术都有一些痴迷,这时候你要清楚你自己的角色,你不是一个程序员,你的目标是管理好一个项目,技术难题要交给能够解决的组员去解决,你更多的精力应该是把控项目的进度质量及与公司领导、用户、客户进行很多的交流工作,同时发挥你的每一个组员的最大能量。

要有一个好的心态

    在项目管理过程中你可能会与某些单位的领导或者公司的老总级人物打交道,这时候要把握好自己的心态,不能太卑微同时也不能太张扬,要把握的有礼有节,这些才能得到这个领导及老总的认可。很重要的一个心态就是我是为你服务的,你付给我公司的钱不是我抢来的,是我劳动付出的。所以也要压制某些用户的不平等的心态,如果是原则性的问题该不让步就是不能让步的,如果是你自己把控不了的交到公司由公司领导来权衡。

posted @ 2009-03-15 11:13 Python 阅读(277) | 评论 (0)编辑 收藏

2009年3月10日

[转载]主动对象(Active Object)

主动对象模式用于降低方法执行和方法调用之间的耦合。该模式描述了另外一种更为透明的任务间通信方法。

传统上,所有的对象都是被动的代码段,对象中的代码是在对它发出方法调用的线程中执行的,当方法被调用时,调用线程将阻塞,直至调用结束。而主动对象却不一样。这些对象具有自己的命令执行线程,主动对象的方法将在自己的执行线程中执行,不会阻塞调用方法。

例如,设想对象"A"已在你的程序的main()函数中被实例化。当你的程序启动时,OS创建一个线程,以从main()函数开始执行。如果你调用对象A的任何方法,该线程将"流过"那个方法,并执行其中的代码。一旦执行完成,该线程返回调用该方法的点并继续它的执行。但是,如果"A"是主动对象,事情就不是这样了。在这种情况下,主线程不会被主动对象借用。相反,当"A"的方法被调用时,方法的执行发生在主动对象持有的线程中。另一种思考方法:如果调用的是被动对象的方法(常规对象),调用会阻塞(同步的);而另一方面,如果调用的是主动对象的方法,调用不会阻塞(异步的)。

由于主动对象的方法调用不会阻塞,这样就提高了系统响应速度,在网络编程中是大有用武之地的。

在这里我们将一个"Logger"(日志记录器)对象对象为例来介绍如何将一个传统对象改造为主动对象,从而提高系统响应速度。

Logger的功能是将一些系统事件的记录在存储器上以备查询,由于Logger使用慢速的I/O系统来记录发送给它的消息,因此对Logger的操作将会导致系统长时间的等待。

其功能代码简化如下:

class Logger: public ACE_Task<ACE_MT_SYNCH>
{
public:
    void LogMsg(const string& msg)
    {
        cout<<endl<<msg<<endl;
        ACE_OS::sleep(2);
    }
};

为了实现实现记录日志操作的主动执行,我们需要用命令模式将其封装,从而使得记录日志的方法能在合适的时间和地方主动执行,封装方式如下:

class LogMsgCmd: public ACE_Method_Object
{
public:
    LogMsgCmd(Logger *plog,const string& msg)
    {
        this->log=plog;
        this->msg=msg;
    }

    int call()
    {
        this->log->LogMsg(msg);
        return 0;
    }

private:
    Logger *log;
    string msg;
};

class Logger: public ACE_Task<ACE_MT_SYNCH>
{
public:
    void LogMsg(const string& msg)
    {
        cout<<endl<<msg<<endl;
        ACE_OS::sleep(2);
    }

    LogMsgCmd *LogMsgActive(const string& msg)
    {
        new LogMsgCmd(this,msg);
    }
};

这里对代码功能做一下简单的说明:

ACE_Method_Object是ACE提供的命令模式借口,命令接口调用函数为int call(),在这里通过它可以把每个操作日志的调用封装为一个LogMsgCmd对象,这样,当原来需要调用LogMsg的方法的地方只要调用LogMsgActive即可生成一个LogMsgCmd对象,由于调用LogMsgActive方法,只是对命令进行了封装,并没有进行日志操作,所以该方法会立即返回。然后再新开一个线程,将LogMsgCmd对象作为参数传入,在该线程中执行LogMsgCmd对象的call方法,从而实现无阻塞调用。

然而,每次对一个LogMsg调用都开启一个新线程,无疑是对资源的一种浪费,实际上我们往往将生成的LogMsgCmd对象插入一个命令队列中,只新开一个命令执行线程依次执行命令队列中的所有命令。并且,为了实现对象的封装,命令队列和命令执行线程往往也封装到Logger对象中,代码如下所示:

#include "ace/OS.h"
#include "ace/Task.h"
#include "ace/Method_Object.h"
#include "ace/Activation_Queue.h"
#include "ace/Auto_Ptr.h"

#include <string>
#include <iostream>
using namespace std;

class Logger: public ACE_Task<ACE_MT_SYNCH>
{
public:
    Logger()
    {
        this->activate();
    }

    int svc();
    void LogMsg(const string& msg);
    void LogMsgActive (const string& msg);

private:
    ACE_Activation_Queue cmdQueue;    //命令队列
};

class LogMsgCmd: public ACE_Method_Object
{
public:
    LogMsgCmd(Logger *plog,const string& msg)
    {
        this->log=plog;
        this->msg=msg;
    }

    int call()
    {
        this->log->LogMsg(msg);
        return 0;
    }

private:
    Logger *log;
    string msg;
};

void Logger::LogMsg(const string& msg)
{
    cout<<endl<<msg<<endl;
    ACE_OS::sleep(2);
}

//以主动的方式记录日志
void Logger::LogMsgActive(const string& msg)
{
    //生成命令对象,插入到命令队列中
    cmdQueue.enqueue(new LogMsgCmd(this,msg));
}

int Logger::svc()
{
    while(true)
    {
        //遍历命令队列,执行命令
        auto_ptr<ACE_Method_Object> mo
            (this->cmdQueue.dequeue ());

        if (mo->call () == -1)
            break;
    }
    return 0;
}

int main (int argc, ACE_TCHAR *argv[])
{
    Logger log;
    log. LogMsgActive ("hello");

    ACE_OS::sleep(1);
    log.LogMsgActive("abcd");

    while(true)
        ACE_OS::sleep(1);

    return 0;
}

在这里需要注意一下命令队列ACE_Activation_Queue对象,它是线程安全的,使用方法比较简单,这里我也不多介绍了。

posted @ 2009-03-10 09:53 Python 阅读(858) | 评论 (0)编辑 收藏

2009年3月9日

[转载]完成端口(I/O completion)

 

异步过程调用(apcs)问题:
    只有发overlapped请求的线程才可以提供callback函数(需要一个特定的线程为一个特定的I/O请求服务)。
完成端口(I/O completion)的优点:
    不会限制handle个数,可处理成千上万个连接。I/O completion port允许一个线程将一个请求暂时保存下来,由另一个线程为它做实际服务。
并发模型与线程池:
    在典型的并发模型中,服务器为每一个客户端创建一个线程,如果很多客户同时请求,则这些线程都是运行的,那么CPU就要一个个切换,CPU花费了更多的时间在线程切换,线程确没得到很多CPU时间。到底应该创建多少个线程比较合适呢,微软件帮助文档上讲应该是2*CPU个。但理想条件下最好线程不要切换,而又能象线程池一样,重复利用。I/O完成端口就是使用了线程池。
理解与使用:
第一步:
在我们使用完成端口之前,要调用CreateIoCompletionPort函数先创建完成端口对象。
定义如下:
HANDLE CreateIoCompletionPort(
                                 HANDLE FileHandle,
                                HANDLE ExistingCompletionPort,
                               DWORD CompletionKey,
                               DWORD NumberOfConcurrentThreads
);
FileHandle:
文件或设备的handle, 如果值为INVALID_HANDLE_VALUE则产生一个没有和任何文件handle有关系的port.( 可以用来和完成端口联系的各种句柄,文件,套接字)
ExistingCompletionPort:
NULL时生成一个新port, 否则handle会加到此port上。
CompletionKey:
用户自定义数值,被交给服务的线程。GetQueuedCompletionStatus函数时我们可以完全得到我们在此联系函数中的完成键(申请的内存块)。在GetQueuedCompletionStatus
中可以完封不动的得到这个内存块,并且使用它。
NumberOfConcurrentThreads:
参数NumberOfConcurrentThreads用来指定在一个完成端口上可以并发的线程数量。理想的情况是,一个处理器上只运行一个线程,这样可以避免线程上下文切换的开销。如果这个参数的值为0,那就是告诉系统线程数与处理器数相同。我们可以用下面的代码来创建I/O完成端口。
隐藏在之创建完成端口的秘密:
1. 创建一个完成端口
CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, dwNumberOfConcurrentThreads);
2. 设备列表,完成端口把它同一个或多个设备相关联。
CreateIoCompletionPort(hDevice, hCompPort, dwCompKey, 0) ;
第二步:
根据处理器个数,创建cpu*2个工作线程:
CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,0, &ThreadID))
与此同时,服务器调用WSASocket,bind, listen, WSAAccept,之后,调用
CreateIoCompletionPort((HANDLE) Accept, CompletionPort... )把一个套接字句柄和一个完成端口绑定到一起。完成端口又同一个或多个设备相关联着,所以以套接字为基础,投递发送和请求,对I/O处理。接着,可以依赖完成端口,接收有关I/O操作完成情况的通知。再看程序里:
WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
&(PerIoData->Overlapped), NULL)开始调用,这里象前面讲过的一样,既然是异步I/O,所以WSASend和WSARecv的调用会立即返回。
系统处理:
当一个设备的异步I/O请求完成之后,系统会检查该设备是否关联了一个完成端口,如果是,系统就向该完成端口的I/O完成队列中加入完成的I/O请求列。
然后我们需要从这个完成队列中,取出调用后的结果(需要通过一个Overlapped结构来接收调用的结果)。怎么知道这个队列中已经有处理后的结果呢,调用GetQueuedCompletionStatus函数。
工作线程与完成端口:
和异步过程调用不同(在一个Overlapped I/O完成之后,系统调用该回调函数。OS在有信号状态下(设备句柄),才会调用回调函数(可能有很多APCS等待处理了))
GetQueuedCompletionStatus
在工作线程内调用GetQueuedCompletionStatus函数。
GetQueuedCompletionStatus(
    HANDLE CompletionPort,
    LPDWORD lpNumberOfBytesTransferred,
    LPDWORD lpCompletionKey,
    LPOVERLAPPED *lpOverlapped,
    DWORD dwMilliseconds
);
CompletionPort:指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个I/O完成端口,所有的I/O请求完成以后的通知都将发给该端口。
lpNumberOfBytesTransferred:传输的数据字节数
lpCompletionKey:
完成端口的单句柄数据指针,这个指针将可以得到我们在CreateIoCompletionPort中申请那片内存。
lpOverlapped:
重叠I/O请求结构,这个结构同样是指向我们在重叠请求时所申请的内存块,同时和lpCompletionKey,一样我们也可以利用这个内存块来存储我们要保存的任意数据。
dwMilliseconds:
等待的最长时间(毫秒),如果超时,lpOverlapped被设为NULL,函数返回False.
GetQueuedCompletionStatus功能及隐藏的秘密:
GetQueuedCompletionStatus使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项或直到超时。(I/0完成队列中出现了记录)调用GetQueuedCompletionStatus时,调用线程的ID(cpu*2个线程,每个ServerWorkerThread的线程ID)就被放入该等待线程队列中。
     等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中。
这样,I/O完成端口内核对象就知道哪些线程正在等待处理完成的I/O请求。当端口的I/O完成队列出现一项时,完成端口就唤醒(睡眠状态中变为可调度状态)等待线程队列中的一个线程。线程将得到完成I/O项中的信息:传输的字节数,完成键(单句柄数据结构)和Overlapped结构地址,线程是通过GetQueuedCompletionStatus返回这些信息,等待CPU的调度。
GetQueuedCompletionStatus返回可能有多种原因,如果传递无效完成端口句柄,函数返回False,GetLastError返回一个错误(ERROR_INVALID_HANDLE),如果超时,返回False, GetLastError返回WAIT_TIMEOUT, i/o完成队列删除一项,该表项是一个成功完成的I/O请求,则返回True。
    调用GetQueuedCompletionStatus的线程是后进先出的方式唤醒的,比如有4个线程等待,如果有一个I/O,最后一个调用GetQueuedCompletionStatus的线程被唤醒来处理。处理完之后,再调用GetQueuedCompletionStatus进入等待线程队列中。
深入分析完成端口线程池调度原理:
    假设我们运行在2CPU的机器上。创建完成端口时指定2个并发,创建了4个工作线程加入线程池中等待完成I/O请求,且完成端口队列(先入先出)中有3个完成I/O的请求的情况:
工作线程运行, 创建了4个工作线程,调用GetQueuedCompletionStatus时,该调用线程就进入了睡眠状态,假设这个时候,I/O完成队列出现了三项,调用线程的ID就被放入该等待线程队列中。
I/O完成端口内核对象(第3个参数等级线程队列),因此知道哪些线程正在等待处理完成的I/O请求。当端口的I/O完成队列出现一项时,完成端口就唤醒(睡眠状态中变为可调度状态)等待线程队列中的一个线程(前面讲过等待线程队列是后进先出)。所以线程D将得到完成I/O项中的信息:传输的字节数,完成键(单句柄数据结构)和Overlapped结构地址,线程是通过GetQueuedCompletionStatus返回这些信息。
在前面我们指定了并发线程的数目是2,所以I/O完成端口唤醒2个线程,线程D和线程C,另两个继续休眠(线程B,线程A),直到线程D处理完了,发现表项里还有要处理的,就唤醒同一线程继续处理。
线程并发量:

   并发量限制了与该完成端口相关联的可运行线程的数目, 它类似阀门的作用。 当与该完成端口相关联的可运行线程的总数目达到了该并发量,系统就会阻塞任何与该完成端口相关联的后续线程的执行,直到与该完成端口相关联的可运行线程数目下降到小于该并发量为止。所以解释了线程池中的运行线程可能会比设置的并发线程多的原因。
    它的作用:
最有效的假想是发生在有完成包在队列中等待,而没有等待被满足,因为此时完成端口达到了其并发量的极限。此时,一个正在运行中的线程调用 GetQueuedCompletionStatus时,它就会立刻从队列中取走该完成包。这样就不存在着环境的切换,因为该处于运行中的线程就会连续不断地从队列中取走完成包,而其他的线程就不能运行了。
注意:如果池中的所有线程都在忙,客户请求就可能拒绝,所以要适当调整这个参数,获得最佳性能。
线程并发:D线程挂起,加入暂停线程,醒来后又加入释放线程队列。
线程的安全退出:
PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。
完成端口使用需要注意的地方:
1.在执行wsasend和wsarecv操作前,请先将overlapped结构体使用memset进行清零。

 

posted @ 2009-03-09 11:52 Python 阅读(650) | 评论 (0)编辑 收藏