如何用C++实现异步文件IO 重叠IO和完成端口技术解析

c++++ 中异步文件 i/o 的实现核心在于使用重叠 i/o 和完成端口技术,以避免线程阻塞。1. 使用 overlapped 结构体发起异步 i/o 请求,2. 创建并关联完成端口以处理完成通知,3. 通过 getqueuedcompletionstatus 等待并处理 i/o 完成结果。此外,需注意错误处理和资源管理,如检查 getlasterror 和关闭句柄。

如何用C++实现异步文件IO 重叠IO和完成端口技术解析

异步文件 I/O 在 c++ 中实现的核心在于让文件操作不阻塞主线程,从而提高程序的响应性和并发性。这通常涉及使用操作系统提供的重叠 I/O (Overlapped I/O) 和完成端口 (Completion Ports) 技术。简单来说,就是发起 I/O 请求后立即返回,让操作系统在后台处理,完成后通知程序。

如何用C++实现异步文件IO 重叠IO和完成端口技术解析

解决方案:

如何用C++实现异步文件IO 重叠IO和完成端口技术解析

首先,我们需要理解重叠 I/O 的概念。重叠 I/O 允许我们发起一个 I/O 操作,而无需等待其完成。我们需要一个 OVERLAPPED 结构体来传递 I/O 请求的相关信息。

立即学习C++免费学习笔记(深入)”;

#include <iostream> #include <fstream> #include <windows.h>  bool AsyncReadFile(HANDLE hFile, LPVOID buffer, DWORD bytesToRead, LPOVERLAPPED pOverlapped) {     return ReadFile(hFile, buffer, bytesToRead, NULL, pOverlapped); }

这段代码只是一个简单的开始,它展示了如何使用 ReadFile 函数发起一个异步读取操作。关键在于最后一个参数 pOverlapped,它告诉操作系统这是一个异步操作。如果 ReadFile 返回 FALSE,并不一定意味着失败,而是可能操作正在进行中,需要检查 GetLastError() 的返回值。如果是 ERROR_IO_PENDING,则表示操作正在异步执行。

如何用C++实现异步文件IO 重叠IO和完成端口技术解析

接下来,我们需要处理 I/O 完成的通知。这就是完成端口发挥作用的地方。

  1. 创建完成端口: 使用 CreateIoCompletionPort 函数创建一个完成端口。

    HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hCompletionPort == NULL) {     std::cerr << "CreateIoCompletionPort failed: " << GetLastError() << std::endl;     return 1; }
  2. 将文件句柄关联到完成端口: 使用 CreateIoCompletionPort 函数将文件句柄与完成端口关联起来。

    HANDLE hFile = CreateFile(L"test.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (hFile == INVALID_HANDLE_VALUE) {     std::cerr << "CreateFile failed: " << GetLastError() << std::endl;     return 1; }  HANDLE hAssociatedPort = CreateIoCompletionPort(hFile, hCompletionPort, (ULONG_PTR)hFile, 0); if (hAssociatedPort == NULL) {     std::cerr << "Associate file handle with completion port failed: " << GetLastError() << std::endl;     return 1; }
  3. 发起异步 I/O 请求: 使用 ReadFile 或 WriteFile 函数发起异步 I/O 请求,并将 OVERLAPPED 结构体传递给函数。

  4. 等待 I/O 完成: 使用 GetQueuedCompletionStatus 函数等待 I/O 完成的通知。

    DWORD bytesTransferred; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped;  BOOL bRet = GetQueuedCompletionStatus(hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE); if (bRet == FALSE) {     std::cerr << "GetQueuedCompletionStatus failed: " << GetLastError() << std::endl;     return 1; }  std::cout << "Bytes transferred: " << bytesTransferred << std::endl;

    GetQueuedCompletionStatus 会一直阻塞,直到完成端口收到一个 I/O 完成的通知。当 I/O 完成时,bytesTransferred 变量会包含实际传输的字节数,completionKey 变量会包含与文件句柄关联的完成键,pOverlapped 变量会包含指向 OVERLAPPED 结构体的指针

  5. 处理 I/O 完成: 在 GetQueuedCompletionStatus 返回后,我们可以处理 I/O 操作的结果。例如,可以检查 bytesTransferred 变量的值,以确定实际传输的字节数。

完整的示例代码:

#include <iostream> #include <fstream> #include <windows.h>  int main() {     HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);     if (hCompletionPort == NULL) {         std::cerr << "CreateIoCompletionPort failed: " << GetLastError() << std::endl;         return 1;     }      HANDLE hFile = CreateFile(L"test.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);     if (hFile == INVALID_HANDLE_VALUE) {         std::cerr << "CreateFile failed: " << GetLastError() << std::endl;         return 1;     }      HANDLE hAssociatedPort = CreateIoCompletionPort(hFile, hCompletionPort, (ULONG_PTR)hFile, 0);     if (hAssociatedPort == NULL) {         std::cerr << "Associate file handle with completion port failed: " << GetLastError() << std::endl;         return 1;     }      char buffer[1024];     OVERLAPPED overlapped = {0};     overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //手动重置事件      if (!ReadFile(hFile, buffer, sizeof(buffer) - 1, NULL, &overlapped)) {         if (GetLastError() != ERROR_IO_PENDING) {             std::cerr << "ReadFile failed: " << GetLastError() << std::endl;             CloseHandle(hFile);             CloseHandle(hCompletionPort);             return 1;         }     }      DWORD bytesTransferred;     ULONG_PTR completionKey;     LPOVERLAPPED pOverlapped;      BOOL bRet = GetQueuedCompletionStatus(hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE);     if (bRet == FALSE) {         std::cerr << "GetQueuedCompletionStatus failed: " << GetLastError() << std::endl;         CloseHandle(hFile);         CloseHandle(hCompletionPort);         return 1;     }      buffer[bytesTransferred] = '';     std::cout << "Read: " << buffer << std::endl;      CloseHandle(hFile);     CloseHandle(hCompletionPort);     CloseHandle(overlapped.hEvent);      return 0; }

这个例子展示了最基本的异步文件读取。需要注意的是,错误处理和资源管理至关重要。

C++ 异步文件 I/O 的性能瓶颈有哪些?

异步 I/O 并非银弹,它也有自身的局限性。频繁的小型 I/O 操作可能会因为上下文切换的开销而降低性能。磁盘本身的 I/O 速度也是一个瓶颈。此外,不正确的缓冲管理和同步机制也可能导致性能下降。因此,需要根据具体的应用场景进行优化。例如,可以使用更大的缓冲区,或者使用多个线程来处理 I/O 请求。

如何处理 C++ 异步文件 I/O 中的错误?

错误处理是异步 I/O 中非常重要的一部分。由于 I/O 操作是在后台执行的,因此我们需要一种机制来检测和处理错误。一种常见的方法是在 OVERLAPPED 结构体中使用事件对象。当 I/O 操作完成时,操作系统会设置事件对象的状态。我们可以使用 WaitForSingleObject 函数等待事件对象的状态变为 signaled。如果 I/O 操作失败,我们可以使用 GetLastError 函数获取错误代码。

另外,GetQueuedCompletionStatus 返回值如果是 FALSE,也需要检查 GetLastError。

异步 I/O 和多线程有什么区别

异步 I/O 和多线程都可以用来提高程序的并发性,但它们的工作方式不同。多线程通过创建多个线程来并发执行任务。每个线程都有自己的和寄存器,因此线程之间的切换需要一定的开销。异步 I/O 则利用操作系统提供的异步 I/O 功能,将 I/O 操作交给操作系统来处理。当 I/O 操作完成时,操作系统会通知程序。异步 I/O 不需要创建额外的线程,因此开销较小。然而,异步 I/O 的编程模型比多线程更复杂。选择哪种方法取决于具体的应用场景。对于 CPU 密集型的任务,多线程可能更适合。对于 I/O 密集型的任务,异步 I/O 可能更适合。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享