您的位置:首页 > 其它

重叠I/O详解

2015-11-08 21:09 267 查看
在windows中有一个api叫readfile



bool readfile(

handle hfile, // handle to
file

lpvoid lpbuffer, // data buffer

dword nnumberofbytestoread, // number of bytes to read

lpdword lpnumberofbytesread, // number of bytes read

lpoverlapped lpoverlapped // overlapped buffer

);



如果我们在createfile的时候没有使用file_flag_overlapped 标志,同时在调用readfile的时候把lpoverlapped lpoverlapped 这个参数设置的是null,那么readfile这个函数的调用一直要到读取完数据指定的数据后才会返回,如果没读取完,就会阻塞在这里。同样 ,writefile和readfile都是这样的。这样在读写大文件的时候,我们很多时间都浪费在等待readfile和writefile的返回上面。如果readfile和writefile是往管道里读写数据,那么有可能阻塞得更久,导致程序性能下降。为了解决这个问题,windows引进了重叠io的概念,同样是上面的readfile和writefile,如果在createfile的时候设置了file_flag_overlapped ,
那么在调用readfile和writefile的时候就可以给他们最后一个参数传递一个overlapped结构。这样readfile或者 writefile的调用马上就会返回,这时候你可以去做你要做的事,系统会自动替你完成readfile或者writefile,在你调用了 readfile或者writefile后,你继续做你的事,系统同时也帮你完成readfile或writefile的操作,这就是所谓的重叠。使用重 叠io还有一个好处,就是你可以同时发出几个readfile或者writefile的调用,然后用waitforsingleobject或者
waitformultipleobjects来等待操作系统的操作完成通知,在得到通知信号后,就可以用getoverlappedresult来查询 io调用的结果。



基本上就这样,。不知道你清楚了没有,,,

至于socket里的重叠io,和这个差不错,不同的是readfile writefile被wsarecv和wsasend所代替了。这其中牵涉到的东西很多,其实有关windows的异步io机制,是很高深的,所以开始我才推荐你去看《windows2000编程内幕》,也可以去看《inside windows2000》



当cpu执行你的代码时遇上一个i/o请求[诸如读写文件之类的],系统产生一个中断,让cpu去完成这个i/o请求,等到完成了以后,系统再次产生一个中断让原先的程序继续运行。也就说通过中断保持这两者间的同步。可以将终端理解为硬件化的信号量。

这就是所谓的同步概念,一个线程中只可能同时处理一个i/o请求

你要知道,一个i/o操作是非常耗时的,当你的代码挂起后等待i/o完成的这段时间内,你的这个线程浪费了n个指令周期。

如果同时要反复读写大文件,用同步的效率是很低的。



为了解决这个问题,当cpu执行你的代码时遇上一个i/o请求后,系统这是为你开一根内部线程去处理i/o请求,并且你的线程并不挂起,但你可能会觉得如果i/o还没完成,后续的代码就算他让我执行,我也执行不下去了嘛?

如果下面的代码和这个i/o操作有关的话,那么它就要等一等,等到这个i/o操作完成,通过在一个线程中调用waitformultiobject()和getoverlappedresult()就可以得到i/o完成的消息,然后再对其作相应的处理。

但如果后续的代码和这个i/o操作无关,你就可以以更快的速度之行下去了,而无需等待io请求的完成了

这也就是异步了




你想当你有这样一个请求,就是

readfile(...)
-1

writefile(...)
-2


readfile(...)
-3

你在程序中如果使用同步的话,那只有当你完成1以后2才会继续执行,2执行完以后3才会继续执行。这就是同步。



当如果使用异步的话,当系统遇到1时,ok,开一线程给它去完成该io请求,然后系统继续运行2,3,分别开两线程。

1-2-3如果是比较耗时的操作,尤其是运用在网络上,那么1-2-3这三个io请求是并行的,也就是重叠的。

重叠i/o就是能够同时以多个线程处理多个i/o,其实你自己开多个线程也可以处理多个i/o,当然系统内部优化以后肯定性能要比你的强,呵呵。



我只是简单的说了一下重叠[overlapped]没从代码的角度给你分析。希望你能对重叠io有所理解。看看windows网络编程,上面不是有模型嘛

最后提一下重叠模型的缺点,他为每一个io请求都开了一根线程,当同时有1000个请求发生,那么系统处理线程上下文[context]切换也是非常耗时的,所以这也就引发了完成端口模型iocp,用线程池来解决这个问题。

文件重叠I/O实例:

I/O设备处理必然让主程序停下来干等I/O的完成,

对这个问题有

方法一:使用另一个线程进行I/O。这个方案可行,但是麻烦。 即 CreateThread(…………);创建一个子线程做其他事情。 Readfile(^…………);阻塞方式读数据。

方法二:使用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用于记录了当前正在操作的文件一些相关信息。

//功能:从指定文件的1500位置读入300个字节

int main()

{

BOOL rc;

HANDLE hFile;

DWORD numread;

OVERLAPPED overlap;

char buf[512];

char szPath=”c:\\xxxx\xxxx”;

hFile = CreateFile( szPath,

GENERIC_READ,

FILE_SHARE_READ|FILE_SHARE_WRITE,

NULL,

OPEN_EXISTING,

FILE_FLAG_OVERLAPPED, // 以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结构中提供的event来解决上面遇到的问题。

注意,你所使用的event对象必须是一个MANUAL型的;否则,可能产生竞争条件。

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++) requests 同时有N个同时读取文件

{

//将同一文件按几个部分按overlapped方式同时读

//注意看QueueRequest函数是如何运做的,每次读16384个块

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;

}

程序流程:

1: N个用户同时读取一个文件的各个部分,且每个用户对应一个重叠对象和事件。

2:调用WaitForMultipleObjects(MAX_REQUESTS, ghEvents, TRUE, INFINITE) 当任何一个用户的读操作完成时,函数停止阻塞。并且ghEvents中对应于的读取数据完毕的用户的事件被激活。

3:调用GetOverlappedResult 取得读取数据完毕的用户编号。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: