windows I/O 基础

发布时间:2024年01月09日

Windows中,同步I/O和异步I/O是两种不同的数据输入输出处理方式,而I/O完成端口(IOCP)是一种特定的高效异步I/O模型。

1、同步I/O(Synchronous I/O)

同步I/O操作要求发起I/O请求的线程等待操作完成才能继续执行。这意味着线程在等待I/O完成时会被阻塞。

Windows中打开文件或者设备都可以使用CreateFile函数,Windows系统为我们封装了底层设备I/O的细节让我们可以像操作文件一样操作串口并口等设备。

优点:

  • 编程模型简单,容易理解和实现。
  • 适合不需要高并发处理的应用。

缺点:

  • 线程在等待I/O完成时无法执行其他任务,可能导致资源浪费。
  • 在处理多个I/O请求时,可能需要多个线程,增加了上下文切换的开销。

示例代码:同步文件读取

#include <windows.h>
#include <iostream>

void ReadFromFile(const wchar_t* filename) {
    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening file" << std::endl;
        return;
    }

    char buffer[128];
    DWORD bytesRead;
    BOOL result = ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
    if (result) {
        std::cout << "Read " << bytesRead << " bytes." << std::endl;
    } else {
        std::cerr << "Error reading file" << std::endl;
    }

    CloseHandle(hFile);
}

备注:注意,在同步模式下,示例中 CreateFile 中 参数 FILE_ATTRIBUTE_NORMAL,也可以是NULL

同步I/O设备

对设配I/O进行读写最方便时ReadFile和WriteFile 函数原型如下:

BOOL ReadFile(
  HANDLE       hFile, // 设备句柄
  LPVOID       lpBuffer, //数据缓存
  DWORD        nNumberOfBytesToRead, // 告诉设备需要读取多少字节
  LPDWORD      lpNumberOfBytesRead, // 真实读取的字节
  LPOVERLAPPED lpOverlapped // 同步I/O 此参数应该为NULL,异步I/O时需要传入LPOVERLAPPED  
);

BOOL WriteFile(
  HANDLE       hFile,// 设备句柄
  LPCVOID      lpBuffer,//数据缓存
  DWORD        nNumberOfBytesToWrite,// 告诉设备需要写入多少字节
  LPDWORD      lpNumberOfBytesWritten,真实写入的字节
  LPOVERLAPPED lpOverlapped// 同步I/O 此参数应该为NULL,异步I/O时需要传入LPOVERLAPPED  
);

2、异步I/O(Asynchronous I/O)

异步I/O允许线程发起一个I/O操作后立即继续执行,不需要等待I/O操作完成。操作系统会在I/O操作完成后通知应用程序。

优点:

  • 提高了线程的利用率,因为线程可以在等待I/O操作完成时执行其他任务。
  • 可以提高应用程序处理多个并发I/O请求的能力。

缺点:

  • 编程模型复杂,需要处理回调或检查I/O操作的状态。
  • 错误处理更加复杂。

示例代码:异步文件读取

#include <windows.h>
#include <iostream>

void CALLBACK FileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
    std::cout << "Read " << dwNumberOfBytesTransfered << " bytes." << std::endl;
}

void ReadFromFileAsync(const wchar_t* filename) {
    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening file" << std::endl;
        return;
    }

    char buffer[128];
    OVERLAPPED ol = {0};
    BOOL result = ReadFileEx(hFile, buffer, sizeof(buffer), &ol, FileIOCompletionRoutine);
    if (!result) {
        std::cerr << "Error reading file" << std::endl;
    }

    SleepEx(INFINITE, TRUE); // Wait for the I/O to complete
    CloseHandle(hFile);
}

3、 I/O完成端口(IOCP

IOCP是Windows提供的一种高效的线程池技术,用于处理大量的异步I/O操作。

优点:

  • 高效地处理数千个并发异步I/O操作。
  • 自动线程管理,根据系统负载动态调整线程数量。
  • 减少了线程上下文切换和同步对象的使用,提高了性能。

缺点:

  • 实现复杂,需要对Windows内核的工作方式有深入理解。
  • 调试困难,错误处理较为复杂。

示例代码:使用IOCP

由于IOCP的实现相对复杂,涉及到创建完成端口、关联文件或套接字句柄、处理完成通知等多个步骤,以下是一个简化的IOCP使用示例,展示了如何创建完成端口、如何将文件句柄与完成端口关联,以及如何在工作线程中处理I/O完成事件。

#include <windows.h>
#include <iostream>

// 工作线程函数
DWORD WINAPI WorkerThread(LPVOID lpParam) {
    HANDLE hCompletionPort = (HANDLE)lpParam;
    DWORD bytesTransferred;
    ULONG_PTR completionKey;
    OVERLAPPED* pOverlapped;

    while (GetQueuedCompletionStatus(hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE)) {
        // 处理完成的I/O请求
        // ...
    }

    return 0;
}

void UseIOCP(const wchar_t* filename) {
    // 打开文件
    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening file" << std::endl;
        return;
    }

    // 创建完成端口
    HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (hCompletionPort == NULL) {
        std::cerr << "Error creating IOCP" << std::endl;
        CloseHandle(hFile);
        return;
    }

    // 将文件句柄与完成端口关联
    if (CreateIoCompletionPort(hFile, hCompletionPort, 0, 0) == NULL) {
        std::cerr << "Error associating file handle with IOCP" << std::endl;
        CloseHandle(hCompletionPort);
        CloseHandle(hFile);
        return;
    }

    // 创建工作线程
    HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hCompletionPort, 0, NULL);
    if (hThread == NULL) {
        std::cerr << "Error creating worker thread" << std::endl;
        CloseHandle(hCompletionPort);
        CloseHandle(hFile);
        return;
    }

    // 发起异步读取操作
    char buffer[1024];
    OVERLAPPED ol = {0};
    if (!ReadFile(hFile, buffer, sizeof(buffer), NULL, &ol)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            std::cerr << "Error reading file" << std::endl;
            CloseHandle(hThread);
            CloseHandle(hCompletionPort);
            CloseHandle(hFile);
            return;
        }
    }

    // 等待工作线程完成
    WaitForSingleObject(hThread, INFINITE);

    // 清理资源
    CloseHandle(hThread);
    CloseHandle(hCompletionPort);
    CloseHandle(hFile);
}

在这个示例中,我们首先打开一个文件并创建一个完成端口。然后,我们将文件句柄与完成端口关联,并创建一个工作线程来处理I/O完成事件。接着,我们发起一个异步读取操作。工作线程将在I/O操作完成时被唤醒,以处理该事件。

请注意,这个示例是高度简化的。在实际应用中,你需要处理多个并发的I/O操作,正确处理I/O操作的完成或失败,并且可能需要管理多个工作线程。此外,你还需要设置OVERLAPPED结构体,以便在I/O操作完成时能够正确地获取到操作的上下文信息。


参考文献:
1、Windows同步设备I/O与异步设备I/O
2、追求极致:Windows异步IO各种模式性能对比
3、浅谈windows下高性能服务器模型IOCP

文章来源:https://blog.csdn.net/weixin_39568531/article/details/135483541
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。