我想在Windows中讨论潜在的异步(Overlapped)I / O实现,因为有很多方法可以实现它。 Windows中重叠的I / O提供了异步处理数据的能力,即操作的执行是非阻塞的。
编辑:这个问题的目的一方面是关于改进我自己实施的讨论,另一方面是关于替代实施的讨论。什么异步I / O实现对并行繁重的I / O最有意义,在小型的大多数单线程应用程序中最有意义。
我会引用MSDN:
同步执行某个功能时,直到操作完成后才会返回。这意味着在等待耗时的操作完成时,可以无限期地阻止调用线程的执行。即使操作尚未完成,调用重叠操作的函数也可以立即返回。这使得在后台执行耗时的I / O操作,同时调用线程可以自由执行其他任务。例如,单个线程可以在不同的句柄上执行同时的I / O操作,甚至可以在同一个句柄上同时执行读写操作。
我认为读者熟悉重叠I / O的基本概念。
异步I / O的另一种解决方案是完成端口,但这不是本讨论的主题。有关其他I / O概念的更多信息,请访问MSDN“About File Management > Input and Output (I/O) > I/O Concepts”
我想在这里展示我的(C / C ++)实现并分享讨论。
这是我的扩展OVERLAPPED结构,名为IoOperation
:
struct IoOperation : OVERLAPPED {
HANDLE Handle;
unsigned int Operation;
char* Buffer;
unsigned int BufferSize;
}
每次调用ReadFile
或WriteFile
等异步操作时,都会创建此结构。 Handle
字段应使用相应的设备/文件句柄进行初始化。 Operation
是一个用户定义的字段,用于说明调用的操作。字段Buffer
是指向先前分配的具有给定大小BufferSize
的内存块的指针。当然,这个结构可以随意扩展。它可以包含操作结果,acutaully transfered size等。
我们需要的第一件事是每次重叠I / O完成时都要发出一个(自动重置)事件句柄。
HANDLE hEvent = CreateEvent(0, FALSE, FALSE, 0);
首先,我决定只对所有异步操作使用一个事件。然后我决定使用RegisterWaitForSingleObject的线程池线程注册此事件。
HANDLE hWait = 0;
....
RegisterWaitForSingleObject(
&hWait,
hEvent,
WaitOrTimerCallback,
this,
INFINITE,
WT_EXECUTEINPERSISTENTTHREAD | WT_EXECUTELONGFUNCTION
);
因此每次发出此事件信号时,都会调用我的回调WaitOrTimerCallback
。
异步操作初始化如下:
IoOperation* Io = new IoOperation(hFile, hEvent, IoOperation::Write, Data, DataSize);
if (IoQueue->Enqueue(Io)) {
WriteFile(hFile, Io->Buffer, Io->BufferSize, 0, Io);
}
每个操作都已排队,并且在我的GetOverlappedResult
回调中成功WaitOrTimerCallback
调用后将被删除。而是在这里一直调用new
,我们可以使用内存池来避免内存碎片并更快地进行分配。
VOID CALLBACK WaitOrTimerCallback(PVOID Parameter, BOOLEAN TimerOrWaitFired) {
list<IoOperation*>::iterator it = IoQueue.begin();
while (it != IoQueue.end()) {
bool IsComplete = true;
DWORD Transfered = 0;
IoOperation* Io = *it;
if (GetOverlappedResult(Io->Handle, Io, &Transfered, FALSE)) {
if (Io->Operation == IoOperation::Read) {
// Handle Read, virtual OnRead(), SetEvent, etc.
} else if (Io->Operation == IoOperation::Write) {
// Handle Read, virtual OnWrite(), SetEvent, etc.
} else {
// ...
}
} else {
if (GetLastError() == ERROR_IO_INCOMPLETE) {
IsComplete = false;
} else {
// Handle Error
}
}
if (IsComplete) {
delete Io;
it = IoQueue.erase(it);
} else {
it++;
}
}
}
当然,为了保证多线程安全,我们在访问I / O队列时需要一个锁保护(关键部分)。
这种实现有优点但也有缺点。
优点:
缺点: