《Win32多线程程序设计》学习笔记---(第一章)
转自 http://www.diybl.com/course/3_program/c++/cppjs/2007918/71872.html
最近开始看《Win32多线程程序设计》 ,感觉受益匪浅,写出来的目的有两个;一是想看看自己理解的程度,能否总结出作者真正想表达的内容。2是与大家共享。好了,废话就不说那么多了!
文章开始讲述了操作系统的演化,作者从早期的MS-DOS1.0版本单任务系统到2.x的可以允许常驻程序(TSR),但是MS-DOS视其为自己的一部分,不是一个应用程序或是一个进程。随后提及到了拥有合作型多任务及抢占式多任务机制的WINDOWS的不同版本!
FAQ01:合作型多任务与抢占式多任务有什么不同--------- 合作型多任务的意思就是说分享CPU是程序的责任,而不是系统的责任,如果有一个程序决定咬住CPU不放,其他程序就没有办法得到执行。抢占式多任务的意思就是操作系统能够强迫应用程序把CPU分享给其他人,程序员不需要额外的努力!
下面介绍2个概念,让我们对进程与线程有一个大体的了解:
进程---从Win32的角度看,进程含有内存和资源。资源包括核心对象(如file handles和线程),USER资源(如对话框和字符串),GDI资源等。进程本身不可以执行,他只是提供一个安置内存和线程的地方。
内存------- 每一个进程都关系到内存。大致分为3类 Code Data Stack .
Code--- 程序的可执行部分,一定是只读性质。这是CPU唯一可以执行的内存。Data是你的程序中所有变量,可以区分为全局变量和静态变量两种。也可以使用malloc和new动态配置内存。Stack是你调用函数时所用的堆栈空间,其中有局部变量。每个线程产生时配有一个堆栈。如果不需要,操作系统会将其动态扩充。所有的内存对进程中的线程都是可用的
=========================================================================================
WIN32多线程程序设计学习笔记(第二章)
转自 http://dev.rdxx.com/VC/Process/2002-5/15/040727495.shtml
《WIN32多线程程序设计》这本书也看了好多遍,但总是觉得印象不深;我想是不是应该做做笔记,一来可以加深一下印象。二也在可以在用时方便查阅。J
首先,我觉得线程一点都不难(这样的心理暗示会使我觉得学的轻松一些);
why?
与调用函数的过程类似;线程只不过用CreateThread的API将函数
封装起来,并产生一个与主程序同时执行的程序来调用被封装的函数。
HANDLE hThread = CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAtt,
DWORD dwStackSize
LPTHREAD_START_ROUTINE lpFun,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadID)
LpFun:就是指向被封装的函数的地址。
LpParameter: 就是指向被封装的函数的参数(没有参数填入null)
CreateThread()除了LpFun,LpParameter两个参数极为重要外,它的其它的参数有啥用的?当然的有用了,具体使用的时候查帮助就行了,实在不会,那就设为null得了。
当调用CreateThread()后,会产生线程核心对象(如果已经产生了线程核心对象,那么就只将此核心对象的引用计数加1;另外,win32系统中有若干种核心对象),并以句柄的方式代表它。那么,显然,在线程使用完毕后就应该用CloseHandle()函数关闭。
既然线程是独立的运行的程序,那么主程序如何知道线程执行完毕呢?答案是通过GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode)函数。如果线程已结束,那么线程结束的代码被放在lpExitCode参数中带回来,如果线程尚未结束,lpExitCode带回来的值是STILL_ACTIVE。
值得注意的是:GetExitCodeThread(HANDLE hThread,LPDWORD lpExitCode)还可以带回线程函数的返回值。lpExitCode就是函数的返回值
例:
线程函数:
DWORD WINAPI ThreadFun(LPVOID n)
{
return 10;
}
int main()
{
hThrd = CreateThread(null,ThreadFun,…..);
DWORD exitCode=0;
for( ; ; )
{
GetExitCodeThread(hThrd,&exitCode);
If ( exitCode == STILL_ACTIVE)
{
//线程仍然在运行
}
else
{
}
}
//exitCode保存了函数的返回值10
printf(“线程函数的返回值: %d /n”, exitCode);
CloseHandle(hThrd);
Return 0;
}
这一章节的内容大概就是这么多,总结一下:
* 产生一个线程 [ CreateThread() ]
* 判断线程是否结束 [ GetExitCodeThread () ]
* 关闭线程 [ CloseHandle () ]
* 强制退出线程 [ ExitThread () ]
===============================================================================================
WIN32多线程程序设计学习笔记(第三章)
转自 http://dev.rdxx.com/VC/Process/2004-3/29/041423641_2.shtml
通过在上一章的学习,我学会使用GetExitCodeThread()这个函数来判断一个线程是否还在执行。通过这个函数,我就可以对付“某个线程必须在某个线程结束后才能运行”的情况。复习一下:
程序片段:
for ( ; ; )
{
int rc;
rc = GetExitCodeThread(hThrd,&exitCode);
if (!rc && exitCode != STILL_ACTIVE)
break;
}
使用这个方法,必须持续不断的调用GetExitCodeThread(),直到其结果不再是STILL_ACTIVE。书上说这方法不好,很浪费cpu时间.,称为忙等待(busy waits)。忙等待必须应有的重视,不然你会发现当包含忙等待的程序运行起来后,其它正在进行的程序的反应就很迟钝了。因为cpu几乎用了所有的可用时间去检查线程是否结束。
那么,我应该怎么做呢?如何才更有效率呢?
另一种有效率的方法:
使用WaitForSingleObject()这个函数来完美的解决问题。
完成上面程序片段同样功能只需一句话:
WaitForSingleObject ( hThrd, INFINITE );
若hThrd线程没有结束时,调用这个函数的程序就会在函数调用点停下来,直到hThrd线程结束时(术语:hThrd被激发)才继续向下执行。
不知道大家会不会有这样疑问------这个函数不会只是把上面的程序片段包装了一下吧?它真是很有效率吗?如果它有效率,它是如何做到的呢?
可以告诉大家,WaitForSingleObject()不是上述程序片段的简单包装,它真的很有效率(不信,按书上的方法,打开性能管理器看看),至于它如何做到的,我也有兴趣知道(知道告诉我),我只知道这个函数得到了操作系统级的某些底层调度函数的支持。
基本上本章的核心内容就是以上所述了。(至于WaitForSingleObject()参数及返回值的说明,查书就行了,毋需多言)
具体的看一个实在的例子,胜过在此长篇大论,下面请看:
//参考书上例子
//程序的目的:只用3个线程,完成6件事
int main()
{
HANDLE hThrds[3];
int slot = 0;
For ( int I=1 ; I<=6 ; I++)
{
if( I > 3 )
{ //已经存在3个线程了
//等待其中的一个线程线束后,再创建线程做剩余的事情
//效率不高,因为线程结束的次序与它们被产生的次序不同
WaitForSingleObject( hThrds[slot] , INFINITE ) ;
CloseHandle ( hThrds[slot] );
}
//构造线程
hThrds[slot] = CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);
if (++slot>2)
slot=0;
}// end for
for ( slot=0; slot<3; slot++)
{
//等待剩余的线程结束
WaitForSingleObject( hThrds[slot] , INFINITE ) ;
CloseHandle ( hThrds[slot] );
}// end for
}
上面的这段程序有一个问题,就是效率不是很高;理想的状况是,一旦有一个线程结束,就立刻产生一个线程补上。仔细读上面的程序,你会发现它无法实现理想的状况;因为它假设线程结束的次序会和它们被产生的次序相同。比如,当依序产生了1,2,3线程以后,一定是按1,2,3的次序结束。其实不然,也许2比1更早结束。在这时,上面的程序并不会立刻产生一个线程填补2,而是非要等1结束了,才产生线程。那么能够实现理想的状况吗?答案是可以,请用WaitForMultipleObjects() 函数, 这个函数的用法基本上与WaitForSingleObject()差不多,在这里我就不举例说明了。
本章最后还提到了MsgWaitForMultipleObjects() , 这个函数特点是在消息到达或线程结束时返回。
我想,只要用会了WaitForSingleObject()这个函数, 那么WaitForMultipleObjects()和MsgWaitForMultipleObjects()看一下也就会用了(书上举了两个小例子来说明,看一下花不了多少时间,不必我在这里废话了)。
这一章节的内容大概就是这么多,总结一下:
* 等待线程结束 [ WaitForSingleObject() ]
* 等待多个线程结束 [ WaitForMultipleObjects() ]
* 等待多个线程结束或消息到达 [ MsgWaitForMultipleObjects() ]
附注:以上正文纯属加强记忆所做笔记,内容或有不详,甚至错误,请阅者见谅,匆见怪!!!
==============================================================================================
WIN32多线程程序设计学习笔记(第四章 上)
转自 http://dev.rdxx.com/VC/Process/2004-3/25/041422956.shtml
到目前为止,我已经知道如何创建线程,销毁线程及如何判断一个线程是否结束了;但运用线程,管理线程,如何使线程按自己意愿正确运行才是多线程程序设计的重点及难点。
大家知道,线程具有并发性,在同一时间段会存在多个线程,当这些线程同时存取同一数据时,就会有问题。就像在超市储物品一样,来的时候物品箱是空,转身拿物品准备储的时候,发现物品箱已被占用了。这时,物品箱就是我所说的同一数据,人指的就是线程了。
那么,如何让线程彼此互相合作,互不干扰呢?------协调线程的工作是由同步机制来完成的。正如书上所说:“有放多种同步机制可以运用。使用哪一种则完全视欲解决的问题而定。这些同步机制还能用各种方式组合在一起运用,以产生更精密的机制”。
首先,让我们看看最容易使用的一个同步机制;它包含同步机制的设计思想,拿握了它也就可以不费什么劲拿握其它的同步机制了。
1 :Critical Sections(关键区域、临界区域)
程序片段:
CRITICAL_SECTION gBoxKey ;
DWORD WINAPI ThreadFun(LPVOID n)
{
//进入关键区域 (情景:关上物品箱,拨下钥匙)
EnterCreiticalSection (&gBoxKey ) ;(1)
//处理一些不可分割的操作。。。。。
//(情景:转身拿物品,储物品,去购物。。。。)
//离开关键区域 (情景:打开物品箱,拿出储存的物品,插上钥匙)
LeaveCreiticalSection (&gBoxKey) ;(2)
}
void main()
{
//初始化全局锁 (情景:生成物品箱的钥匙)
InitializeCriticalSection( &gBoxKey ) ;
//产生两个线程 (情景:准备两个人抢一个物品箱)
HANDLE hMan1 = CreateThread(NULL,0,ThreadFun,…….);
HANDLE hMan2 = CreateThread(NULL,0,ThreadFun,…….);
CloseHandle(hMan1);
CloseHandle(hMan2);
//删除全局锁 (情景:删除物品箱的钥匙)
DeleteCriticalSection( &gBoxKey ) ;
}
按照上面的程序,我在笔记开头所描述的“超市储物品”的情况是不会发生的,不会再有物品箱被人抢走的可能。由于线程的并发性,从我看到一个空箱子到我将物品的放入物品箱中的这段时间内的操作应该是不可分割的;否则,我看到的物品箱就随时会被人抢走。那么如何保证操作是连续、不可分割呢?简单:看到空箱子,关上箱子,拨下钥匙(1),这时,别人就没办法使用这个箱子了。当我不再需要箱子的时候,打开箱子,插入钥匙(2),就可以让别人使用了。上述就是多线程处理共享资源的一个基本思路。
2 :Mutexes(互斥器)
程序片段:
HANDLE hBoxKey ;
DWORD WINAPI ThreadFun(LPVOID n)
{
//进入关键区域 (情景:关上物品箱,拨下钥匙)
WaitForSingleObject ( hMutex,INFINITE ) ;(1)
//处理一些不可分割的操作。。。。。
//(情景:转身拿物品,储物品,去购物。。。。)
/离开关键区域 (情景:打开物品箱,拿出储存的物品,插上钥匙)
ReleaseMutex ( hMutex ) ;(2)
}
void main()
{
//初始化全局锁 (情景:生成物品箱的钥匙)
hBoxKey = CreateMutex( NULL,FALSE,NULL );
//产生两个线程 (情景:准备两个人抢一个物品箱)
HANDLE hMan1 = CreateThread(NULL,0,ThreadFun,…….);
HANDLE hMan2 = CreateThread(NULL,0,ThreadFun,…….);
CloseHandle(hMan1);
CloseHandle(hMan2);
//删除全局锁 (情景:删除物品箱的钥匙)
CloseHandle( hBoxKey ) ;
}
看到没?无论从共享资源的思路了,还是从程序代码的编制上,使用Mutexes与使用Critical Sections几乎都没有什么区别;但作为 Mutexes机制的提出肯定是有其原因的;我们来看这样的一个情形,当我拿走钥匙以后,因为某些因素再也不能回来了,那么这个箱子便再也不能被使用。也就是说,进入Critical Sections线程若中途当掉了,那么别了线程是再也进不了Critical Sections(一个资源就这样浪费了),那些需要进入Critical Sections的线程会停在入口不再执行,线程永远都结束不了。
怎么办?(用WaitForSingleObject解决)
还记得上一章学过的WaitForSingleObject吗?上一章主要用它等待线程的结束,但这个函数的作用不仅限于此,在这里,我们再前进一小步,探究WaitForSingleObject这个函数的妙用。
原型:
DWORD WaitForSingleObject ( HANDLE hObject ,DWORD time ) ;
hObject:等待对象的handle(代表一个核心对象),在上一章中代表线程handle,注意没有,hObject是代表一个核心对象,也就是说,只要是核心对象,WaitForSingleObject都能处理,处理的原则是“当核心对象被激发时,函数返回”(有多少种核心对象?及核心对象对激发的定义见书P76)。
在这里,我遇到了一个叫mutex核心对象,mutex对激发的定义是:“当没有任何线程拥有该mutex,而且有一个线程正以Wait…()等待该mutex,该mutex就会短暂地出现激发状态,使Wait…()得以返回, 那么在其它的情况,mutex处于未激发状态 ”。
好了,我们又进一步的了解了WaitForSingleObject函数,那么,如何解决Critical Sections所遇到的因难呢?当拥有mutex的线程结束前没有调用ReleaseMutex(不管该线程是当了,还是忘记调用ReleaseMutex),那么其它正以WaitForSingleObject()等待此mutex的线程就会收到WAIT_ABANDONED_0。有了这个值,我就解开难题了。
程序片段:
DWORD rc = WaitForSingleObject( hMutex ) ;
If ( rc == WAIT_OBJECT_0 )
{
//正常的处理
}
else
{
If ( rc == WAIT_ABANDONED_0 )
{
//没有正常的释放mutex(比如拥有mutex的线程死了)
}
}
我想,从笔记的角度来看,上面对mutex描述已经可以帮助我很快的上手使用了,至于对mutex更详细的说明,还是要看看书.。(当然,希望我的这一段描述能帮助你理解mutexJ)
总结一下:
CRITICAL_SECTION Mutex 核心对象
InitializeCriticalSection() CreateMutex()
OpenMutex()
EnterCriticalSection() WaitForSingleObject()
LeaveCriticalSection() ReleaseMutex()
DeleteCriticalSection() CloseHandle()
附注:以上正文纯属加强记忆所做笔记,内容或有不详,甚至错误,请阅者见谅,匆见怪!!!
=================================================================================================
WIN32多线程程序设计学习笔记(第四章 下 第五章)
转自 http://dev.rdxx.com/VC/Process/2004-5/3/041435368.shtml
终于又看<<WIN32多线程程序设计>>了;开卷有益,今天看了第四章后半部分,感觉颇有收获;以前对EVENT这个核心对象,用起来总是心中没底,看了EVENT的介绍也是似懂非懂的。哈!今天看了以后,真有神助,我觉得应该是搞懂了(自以为事J)。仔细想想,应该是记笔记的功劳,用脑筋写过的东西还真是不容易忘记咧!!!不但印象深刻,而且助我理解了EVENT。
好了,闲话少说,进入正题吧!
从第三章以来,每一章都必不可少的用到了一个重要的函数,知道是什么吗?
――――WaitFor…. ()系列
//快速复习
判断一个线程是否结束:WaitForSingleObject ( HANDLE hthred …. );
判断是否能够进入关键区域:WaitForSingleObject ( hMutex….) ;
Wait….()会在核心对象被激发时返回,各种核心对象对激发的定义不甚相同,对于hthred而言,线程结束,意味着核心对象被激发;对于hMutex而言, hMutex不再被其它任何线程使用,意味着核心对象被激发。反正对于各种核心对象而言,一定是有某种场景的出现使得核心对象被激发,除了EVENT这个核心对象。
对于EVENT这个核心对象而言,它的激发状态完全由程序来控制,也就是说,由自己来控制EVENT的激发或未激发状态( 通过SetEvent() , ResetEvent() )。当线程1因调用Wait…(hEvent)而被阻塞后,一定是某个线程调用了SetEvent( hEvent )使hEvent被设为激发状态,从而使线程1被解除阻塞继续向下运行,具体的运用参见下表:
函数 | EVENT对象[Manual方式产生] | EVENT对象[Auto方式产生] |
Wait…() | 当EVENT对象变为激发状态(使得因调用Wait…()而等待的线程被唤醒)之后,不会被重置为非激发状态(必须调用ResetEvent()) | 当EVENT对象变为激发状态(使得因调用Wait…()而等待的线程被唤醒)之后,自动重置为非激发状态 |
SetEvent() | 把EVENT对象设为激发状态 | 把EVENT对象设为激发状态 |
ResetEvent() | 把EVENT对象设为非激发状态 | 把EVENT对象设为非激发状态 |
PulseEvent() | 把EVENT对象设为激发状态,唤醒“所有”等待中的线程,然后把EVENT对象设为非激发状态 | 把EVENT对象设为激发状态,唤醒“一个”等待中的线程,然后把EVENT对象设为非激发状态 |
[使用CreateEvent ()API函数构造EVENT核心对象,CreateEvent ()的第二个参数决定了产生的EVENT对象是Manual(手工)方式还是Auto(自动)方式;第三个参数决定了决定了产生的EVENT对象初始状态是激发还是未激发]
EVENT核心对象的不同的状态(初始状态是激发还是未激发)及不同的类型(Manual[手工]方式还是Auto[自动]),与之对应的处理方式也就不一样,情况太多了真是不好一一举例,以后补上J(在下面第五章中,我举了一个例子)
值得注意:使用PulseEvent()方法激发EVENT的请求可能会被遗失,由此可能产生的一系列问题在P124有很精彩的描述,值得一看(由此描述引出了semaphore同步机制)。
至此,第四章差不多可以结束了(除了semaphore,Interlocked ,自己看一下吧!)
下面进入第五章:
如何强制结束一个线程?查查有关线程的API函数,立刻便找到一个结束线程的函数―――――TerminateThread ( ) ; 用它吗?
噢!不要使用它。被TerminateThread ( )强制结束的线程可能会有造成以下后果:
1、 没有机会在自己结束前释放自己所使用的资源
2、 可能引起内存泄漏
3、 如果这个线程被结束时处于一个critical section之中,那么该critical section将因此永远处于锁定状态。
那么如何强制结束一个线程?书上给出了一个例子,以后强制结束一个线程就靠它了。
程序片段:
//线程退出事件
HANDLE hExitEvent = null ;
//一个需要长时间运行的线程
DWORD WINAPI ThreadFun ( LPVOID p )
{
for ( int I =0 ; I < 1000000; I++ )
{
//判断线程是否要被强制结束
/* 可能在这里大家有点疑惑 ,如果没有调用SetEvent()的方法,
hExitEvent不是总处于未激发状态吗?哪线程不就停在这里不动了?
答案是:这里用到了P74提到的一个Wait….()一个用法,当time_out
为0,检查hExitEvent的状态,如果hExitEvent处于未激发状态,
立刻返回WAIT_TIMEOUT,如果hExitEvent处于激发状态,则立刻
返回WAIT_OBJECT_0。*/
if ( WaitForSingleObject ( hExitEvent , 0 ) !=WAIT_TIMEOUT )
{
//做一些退出线程前的清理工作
return (DWORD) –1 ;
}
//做一些极其消耗时间的处理….
//…….
//……..
}
}
void main()
{
HANDLE hThrd;
//构造EVENT核心对象,初始状态处于未激发状态,Manual方式
hExitEvent = CreateEvent ( NULL,TRUE,FALSE,NULL ) ;
//线程启动
hThrd = CreateThread ( null,ThreadFun,…..);
//等待了很久,实在等不下去了,发出结束线程退出事件(激发EVENT核心对象)
//迫使线程结束
SetEvent ( hExitEvent ) ;
//等待线程结束
WaitForSingleObject (hThrd,… );
CloseHandle ( hThrd );
}
下面接着谈到了线程的优先权,调整线程优先权很简单,无非就是用SetThreadPriority()和GetThreadPriority()等函数。但如何有效,合理的调整线程的优先权却是一个复杂的问题,正如书上所说,“如果你的目标是保持简单,那就还是避免处理[优先权]这个烫山芋吧“。
附注:以上正文纯属加强记忆所做笔记,内容或有不详,甚至错误,请阅者见谅,匆见怪!!!
=================================================================================================
WIN32多线程程序设计学习笔记(第六章 上)
本文转自 http://dev.rdxx.com/VC/Process/2002-5/17/040750382.shtml
我们知道当程序调用I/O设备处理一些事情时,让主程序停下来干等I/O的完成是没有效率的。对这个问题有下面几种解决方法:
方法一:使用另一个线程进行I/O。但从以前的学习中也知道,协调线程间的关系是一件麻烦的事情,需要小心的设计;所以这个方案可行,但是麻烦。
方法二:使用overlapped I/O。正如书上所说:“overlapped I/O是WIN32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成overlapped I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价”。哈!这真是一个好方法!!!
下面的内容就是来谈怎样使用overlapped I/O:
进行I/O操作时,指定overlapped方式
使用CreateFile(),将其第6个参数指定为FILE_FLAG_OVERLAPPED,就是准备使用overlapped的方式构造或打开文件;如果采用overlapped,那么ReadFile()、WriteFile()的第5个参数必须提供一个指针,指向一个OVERLAPPED结构。OVERLAPPED用于记录了当前正在操作的文件一些相关信息。
//下面引用书上的例子,来说明overlapped:
//功能:从指定文件的1500位置读入300个字节
int main()
{
BOOL rc;
HANDLE hFile;
DWORD numread;
OVERLAPPED overlap;
char buf[512];
char szPath=”x://xxxx/xxxx”;
//检查系统,确定是否支持overlapped,(NT以上操作系统支持OVERLAPPED)
CheckOsVersion();
// 以overlapped的方式打开文件
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
// OVERLAPPED结构实始化为0
memset(&overlap, 0, sizeof(overlap));
//指定文件位置是1500;
overlap.Offset = 1500;
rc = ReadFile(hFile,buf,300,&numread,&overlap);
//因为是overlapped操作,ReadFile会将读文件请求放入读队列之后立即返回(false),
//而不会等到文件读完才返回(true)
if (rc)
{
//文件真是被读完了,rc为true
// 或当数据被放入cache中,或操作系统认为它可以很快速地取得数据,rc为true
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{//当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中
//等候,直到文件读完
WaitForSingleObject(hFile, INFINITE);
rc = GetOverlappedResult(hFile,&overlap,&numread,FALSE);
//上面二条语句完成的功能与下面一条语句的功能等价:
// GetOverlappedResult(hFile,&overlap,&numread,TRUE);
}
else
{
//出错了
}
}
CloseHandle(hFile);
return EXIT_SUCCESS;
}
读过上面的程序,对overlapped就有一个大概的印象;接着我们继续探索overlapped I/O的强大功能。
在实际工作中,可能会有多个操作同时使用同一个文件handle,那么上面的程序就不再适用了。
怎么办?我们可以利用OVERLAPPED结构中提供的event来解决上面遇到的问题。注意,你所使用的event对象必须是一个MANUAL型的;否则,可能产生竞争条件。原因见书P159。
//程序片段:
int main()
{
int i;
BOOL rc;
char szPath=”x://xxxx/xxxx”;
// 以overlapped的方式打开文件
ghFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
for (i=0; i<MAX_REQUESTS; i++)
{
//将同一文件按几个部分按overlapped方式同时读
//注意看QueueRequest函数是如何运做的
QueueRequest(i, i*16384, READ_SIZE);
}
// 等候所有操作结束;
//隐含条件:当一个操作完成时,其对应的event对象会被激活
WaitForMultipleObjects(MAX_REQUESTS, ghEvents, TRUE, INFINITE);
// 收尾操作
for (i=0; i<MAX_REQUESTS; i++)
{
DWORD dwNumread;
rc = GetOverlappedResult(
ghFile,
&gOverlapped[i],
&dwNumread,
FALSE
);
CloseHandle(gOverlapped[i].hEvent);
}
CloseHandle(ghFile);
return EXIT_SUCCESS;
}
//当读操作完成以后,gOverlapped[nIndex].hEvent会系统被激发
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
//构造一个MANUAL型的event对象
ghEvents[nIndex] = CreateEvent(NULL, TRUE, FALSE, NULL);
//将此event对象置入OVERLAPPED结构
gOverlapped[nIndex].hEvent = ghEvents[nIndex];
gOverlapped[nIndex].Offset = dwLocation;
for (i=0; i<MAX_TRY_COUNT; i++)
{
//文件ghFile唯一
rc = ReadFile(ghFile, gBuffers[nIndex],&dwNumread,&gOverlapped[nIndex]);
//处理成功
if (rc) return TRUE;
err = GetLastError();
if (err == ERROR_IO_PENDING)
{
//当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中
return TRUE;
}
// 处理一些可恢复的错误
if ( err == ERROR_INVALID_USER_BUFFER ||
err == ERROR_NOT_ENOUGH_QUOTA ||
err == ERROR_NOT_ENOUGH_MEMORY )
{
Sleep(50);
continue;//重试
}
// 如果GetLastError()返回的不是以上列出的错误,放弃
break;
}
return -1;
}
嗨!当大家耐着性子看到这里肯定都在骂,这写的什么东西,照本宣科嘛。唉,没办法,这个东西不好举例子,只有写两个程序片段,加强记忆了,见谅、见谅!!!
==============================================================================================
WIN32多线程程序设计学习笔记(第六章下)
继续。。。
上面的程序片段使用WaitForMultipleObjects函数来等待,会有两个问题:
第一、 只能等待小于64个的对象,这是WaitForMultipleObjects函数本身所带来的限制。
第二、 你必须不断根据“哪一个handle被激发”而计算如何处理。那个这个程序的结构可不是很清晰,以后维护可不方便
那么书上在这里提供了一个异步过程调用(APCs)的方法来解决这些问题
APCs的核心观点是:提供一个函数,当overlapped I/O操作完成时,自动调用此函数
注意:运用APCs,必须使用ReadFileEx()、WriteFileEx ()
例子:
//自定义函数,供overlapped I/O操作完成时调用
VOID WINAPI MyFunc(DWORD dwErrorCode, //完成码
DWORD dwNumberOfBytesTransfered, // 被传递的字节数目
LPOVERLAPPED lpOverlapped //指向OVERLAPPED 结构的指针
)
{
//这里运用了一个技巧,因为使用APCs技术;那么OVERLAPPED 结构的
//event栏位没有什么用,可以用它来传递一些参数。
//在这里利用它传递序号,以表明是谁完成了overlapped I/O
int nIndex = (int)(lpOverlapped->hEvent);
//针对nIndex,做一些操作
switch ( nIndex )
{
case 1 : //做一些操作。。。 break;
case 2 : //做一些操作。。。 break;
…..
}
//如果所有overlapped I/O都处理完毕,将全局event激发,使主程序结束
if (++nCompletionCount == MAX_REQUESTS)
SetEvent(ghEvent);
}
int main()
{
//构造全局event
ghEvent=CreateEvent(NULL,TRUE,FALSE, NULL );
// 以overlapped的方式打开文件
ghFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
for (i=0; i<MAX_REQUESTS; i++)
{
//将同一文件按几个部分按overlapped方式同时读
//注意看QueueRequest函数是如何运做的
QueueRequest(i, i*16384, READ_SIZE);
}
// 等待所有操作完成
WaitForSingleObjectEx(ghEvent, INFINITE, TRUE );
CloseHandle(ghFile);
return EXIT_SUCCESS;
}
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
//记录overlapped的序号
gOverlapped[nIndex].hEvent = (HANDLE)nIndex;
//记录文件开始点
gOverlapped[nIndex].Offset = dwLocation;
//当读文件完成后自动调用MyFun
ReadFileEx(ghFile,gBuffers[nIndex],dwAmount,&gOverlapped[nIndex],MyFunc);
}
以上的三个程序片段都是对overlapped I/O具体运用;对付一般的I/O操作应是可以了,如果你需要更高的效率,那么你就需要书上提到的I/O Completion Ports 了。
I/O Completion Ports 也许是最好的方法了(看看书P172-P173你是否会有这种感觉呢?);
但I/O Completion Ports 好像很难理解,我试着从自己理解的角度来写写心得(也许不对);
描述:
把I/O Completion Ports看作容器,那么在这个容器中放置若干个线程(书上说最好是cpu个数*2+2),这些个线程随时随地的将被激活去服务I/O请求。
为什么I/O Completion Ports会很有效率?我是这样想的:
首先容器中保持的线程可以随时承担服务I/O请求的任务,其主要特点是:两个线程在不同的时间可以服务同一个I/O请求;
第二:这些个线程的调度由系统选择安排。系统总是在它认为最合适的时机去调度线程做最合适的事。书上还说了其它的一些有利于效率的工作,我就不再一一描述了。
下面我们来看一下运用I/O Completion Ports这是咱们最关心的:
在P179有一个操作概观:对着它,我来解释书上I/O Completion Ports 的例子。
//注意:一定要对着书,把下面用到的WIN32函数的每个参数的含义弄清楚,这很重要
//――――――产生一个I/O completion port ①
//构造一个I/O completion port
ghCompletionPort = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL, //不使用任何port
0, //这个参数用于在线程间传递参数,此时设为空
0 // 使用默认的线程数
);
//――――――让它和一个文件handle产生关联 ②
//将socket关联到①产生的I/O completion port,那么以后发生在这个socket
//上的任何I/O操作,都由此I/O completion port中的线程处理
CreateIoCompletionPort( (HANDLE)newsocket, //注意,这里是socket;原因见书P184
ghCompletionPort, //指定的I/O completion port
(DWORD)pKey, //一个指针,指向自定义的结构
0 // 使用默认的线程数
);
/* pKey,用于记录在newsocket上发生的操作,在这里指向一个自定义的结构
,此结构中保存从客户端读入的数据和写回客户的数据*/
//――――――产生一堆线程 ③
CreateWorkerThreads();
//――――――下面是④、⑤步
//下面这段代码在线程函数ThreadFunc中
for (;;)
{
//――――――让每一个线程都在completion port上等待 ④
//下面这个函数相当于WaitForSingleObject;[提醒一下:这种等待不是忙等待]
//当一个线程读操作完成后,此函数返回
bResult = GetQueuedCompletionStatus(
ghCompletionPort, //指定在哪一个端口上等待
&dwNumRead,
&(DWORD) pCntx, //一个指针,收到CreateIoCompletionPort所定义的key
&lpOverlapped,
INFINITE
);
/*说明:pCntx就是CreateIoCompletionPort()中参数pkey;读出的数据被指定放入
pkey->InBuffer中 ,仔细看看程序代码及p183的叙述,你就会发现在I/O Completion Ports
中的线程是通过pkey来判别不同的客户端的*/
if (bResult==读完 )
{ //将读出的数据保存到pCntx->OutBuffer中
// 注意:pCntx->nOutBufIndex记录着当前客户端已读出的数据的位置
char *pch = &pCntx->OutBuffer[pCntx->nOutBufIndex++];
*pch++ = pCntx->InBuffer[0];
*pch = '/0';
if (pCntx->InBuffer[0] == '/n')
{ //如果读完了,将收到的信息写回客户端
//――――――开始对着那个文件handle发出一些overlapped I/O请求 ⑤
WriteFile(
(HANDLE)(pCntx->sock),
pCntx->OutBuffer,
pCntx->nOutBufIndex,
&pCntx->dwWritten,
&pCntx->ovOut
);
pCntx->nOutBufIndex = 0;
}
//没有读完
//――――――开始对着那个文件handle发出一些overlapped I/O请求 ⑤
/*下面这一段我将书上的IssueRead(pCntx)拿掉了,替换它的是IssueRead(pCntx)函数的实现*/
//读操作,引发I/O completion port的操作
bResult = ReadFile(
(HANDLE)pCntx->sock, //记取客户端的socket
pCntx->InBuffer, //将从客户端的中读取的字符写入InBuffer
1, //每次读取一个字符
&numRead,
&pCntx->ovIn //嘿!这家伙在这没啥用
);
}
上面程序片段没有列出“避免Completion Packets”的程序代码。
当写入操作完成时,I/O Completion port将收到一个Packets以说明写入操作成功与否;如果写入操作的结果(成功或失败)不是很重要,那么我们肯定不希望在每次写入操作后都接收这样一个Packets(因为浪费时间),我要屏蔽它,可以向下面这样:
OVERLAPPED overlap;
//对hEvent向下面这样处理
overlap.hEvent = CreateEvent(…);
overlap.hEvent = (HANDLE)((DWORD)overlap.hEvent | 0x1);
//那么 “写操作”使用这个经过处理的 overlap后,I/O Completion port不会再发Packets
WriteFile(….&overlap);
限于我的表达能力,有些地方只能生硬的描述出来;这篇笔记的作用是帮助我下次再看的时候能够很快的拿住重点,对你而言可能也没什么用,但我为什么要写出来呢?因为写笔记(我也不知道这能不能算是笔记)能够帮助我更好的理解书上的内容,这一点我深有体会。回头再看一下书,发现书上把这些技术描述的非常清楚。用词也很准确,寥寥数笔直取技术核心。所以建议大家把书反复的看,你会突然开窍J(嗯!我就是这样突然开窍)。
下一篇笔记我争取写的好一些,希望写得不光我明白,也让大家明白!!!
附注:以上正文纯属加强记忆所做笔记,内容或有不详,甚至错误,请阅者见谅,匆见怪!!!