Win32编程之异步完成IO(十)
一、文件的异步写入
#include <Windows.h>
#include <stdio.h>
int main() {
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("无法打开文件。错误码:%d\n", GetLastError());
return 0;
}
OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
CloseHandle(hFile);
printf("文件写入失败!\n");
return 0;
}
CloseHandle(hFile);
return 1;
}
二、文件的异步读取
#include <Windows.h>
#include <stdio.h>
int main() {
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("无法打开文件。错误码:%d\n", GetLastError());
return 0;
}
OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
CloseHandle(hFile);
printf("文件读取失败!\n");
return 0;
}
printf("readBuffer:%s\n", readBuffer);
CloseHandle(hFile);
return 1;
}
三、异步读写操作的判断方法
(1).异步文件写入操作的判断
OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("正在进行异步写入操作!\n");
}
else {
printf("文件写入失败!\n");
CloseHandle(hFile);
return 0;
}
}
(2).异步文件读取操作的判断
OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("正在进行异步读取操作!\n");
}
else {
printf("文件读取失败!\n");
CloseHandle(hFile);
return 0;
}
}
printf("readBuffer:%s\n", readBuffer);
三、异步IO完成通知的方法
1.触发设备内核对象
触发设备内核对象:允许一个线程发出IO请求,另一个线程对结果进行处理,当向一个设备同时发出多个IO请求的时候,此方法无效
(1).等待文件写入完毕
OVERLAPPED ol1 = { 0 };
char buffer[] = "Hello World";
DWORD writeCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("正在进行异步写入操作!\n");
WaitForSingleObject(hFile, INFINITE);
printf("异步写入完毕!\n");
}
else {
printf("文件写入失败!\n");
CloseHandle(hFile);
return 0;
}
}
(2).等待文件读取完毕
OVERLAPPED ol2 = { 0 };
char readBuffer[255] = { 0 };
DWORD readCount = 0;
ret = ReadFile(hFile, readBuffer, 255, &readCount, &ol2);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("正在进行异步读取操作!\n");
WaitForSingleObject(hFile, INFINITE);
printf("异步读取完毕\n");
}
else {
printf("文件读取失败!\n");
CloseHandle(hFile);
return 0;
}
}
printf("readBuffer:%s\n", readBuffer);
2.触发事件内核对象
这种方法允许我们向一个设备同时发出多个IO请求,它允许一个线程发出IO请求,另一个线程对结果进行处理
示例代码:
#include <Windows.h>
#include <stdio.h>
int main() {
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("无法打开文件。错误码:%d\n", GetLastError());
return 0;
}
OVERLAPPED ol1 = { 0 };
ol1.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
OVERLAPPED ol2 = { 0 };
ol2.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
char buffer[] = "Hello World";
char readBuffer[256] = { 0 };
DWORD writeCount = 0;
DWORD readCount = 0;
BOOL ret = WriteFile(hFile, buffer, strlen(buffer), &writeCount, &ol1);
ret = ReadFile(hFile, readBuffer, strlen(readBuffer), &readCount, &ol2);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
HANDLE handle[2];
handle[0] = ol1.hEvent;
handle[1] = ol2.hEvent;
DWORD objnum = WaitForMultipleObjects(2, handle, TRUE, INFINITE);
switch (objnum) {
case WAIT_OBJECT_0: {
printf("文件写入操作完毕\n");
}
case WAIT_OBJECT_0 + 1: {
printf("文件读取操作完毕:%s\n", readBuffer);
}
default:
break;
}
CloseHandle(hFile);
return 0;
}
else {
printf("File failed\n");
}
}
CloseHandle(ol1.hEvent);
CloseHandle(ol2.hEvent);
CloseHandle(hFile);
return 1;
}
3.使用可提醒IO
这种方法允许我们向一个设备发出多个IO请求,发出IO请求的线程必须对结果进行处理
示例代码:
#include <Windows.h>
#include <stdio.h>
char readBuffer[256] = { 0 };
char writeBuffer[256] = "1234567890";
VOID WriteFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
printf("文件写入结束\n");
}
VOID ReadFunc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
printf("%s\n", readBuffer);
}
int main() {
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("无法打开文件。错误码:%d\n", GetLastError());
return 0;
}
OVERLAPPED ol2 = { 0 };
BOOL ret = WriteFileEx(hFile, writeBuffer, strlen(writeBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)WriteFunc);
SleepEx(1000, true);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("文件写入中!!!\n");
}
else {
printf("文件写入失败!!!\n");
CloseHandle(hFile);
return 0;
}
}
ret = ReadFileEx(hFile, readBuffer, sizeof(readBuffer), &ol2, (LPOVERLAPPED_COMPLETION_ROUTINE)ReadFunc);
//SleepEx(1000, true);
WaitForSingleObjectEx(hFile, INFINITE, true);
if (!ret) {
DWORD err = GetLastError();
if (ERROR_IO_PENDING == err) {
printf("文件读取中!!!\n");
} else {
printf("文件读取失败!!!\n");
CloseHandle(hFile);
return 0;
}
}
CloseHandle(hFile);
return 1;
}
4.使用IO完成端口
这种方法允许我们向一个设备同时发出多个IO请求。它允许一个线程发出IO请求,另一个线程对结果进行处理,推荐使用,伸缩性和灵活性都很好,IO完成端口的初衷就是与线程池配合使用
示例代码:
#include <Windows.h>
#include <stdio.h>
int main() {
char userName[256] = { 0 };
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
printf("文件创建失败,错误码:%d\n", GetLastError());
return 1;
}
HANDLE hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == hCicp) {
printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
ULONG_PTR CK_READ = 0;
CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);
OVERLAPPED ol = { 0 };
ReadFile(hFile, userName, 256, NULL, &ol);
DWORD transferedByte = 0;
void* lpContext = NULL;
OVERLAPPED* pOl = NULL;
while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
printf("%s\n", userName);
}
CloseHandle(hFile);
CloseHandle(hCicp);
return 0;
}
四、串行模型与并发模型


CPU是通过时间片轮转的方式执行线程,比如说,A线程执行一段时间,然后切换到B线程,B线程执行一段时间,再切换到C线程,C线程执行一段时间,然后再切换到A线程;当CPU执行的线程过多时,可以发现,CPU处理排队线程的请求远远小于线程之间来回切换的时间,这个时候CPU貌似十分繁忙(忙着切换线程,没有去处理线程请求)
1.IO完成端口


(1).CreateIoCompletionPort函数
CreateIoCompletionPort 函数是 Windows API 中的一个函数,用于创建输入/输出完成端口(I/O Completion Port),这是一种高效的异步 I/O 操作管理机制,常用于多线程的异步 I/O 编程。
函数原型:
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD NumberOfConcurrentThreads
);
参数解释:
ExistingCompletionPort:可选参数,如果要将文件句柄关联到现有的 I/O 完成端口,则传递现有的 I/O 完成端口句柄,否则传递NULL。FileHandle:要与 I/O 完成端口关联的文件句柄。CompletionKey:关联的完成键,是一个用户定义的值,通常用于标识关联的文件句柄。NumberOfConcurrentThreads:指定可以同时执行 I/O 操作的线程数目。
返回值:
如果函数调用成功,将返回新创建的 I/O 完成端口的句柄(HANDLE)。如果函数失败,则返回 NULL,您可以通过调用 GetLastError 获取错误信息。
(2).GetQueuedCompletionStatuse函数
GetQueuedCompletionStatus 函数是 Windows API 中用于从 I/O 完成端口 (I/O Completion Port) 中获取已完成的 I/O 操作的函数。它通常用于与异步 I/O 操作相关的多线程编程,以便检查和处理已完成的 I/O 操作结果。
函数原型:
BOOL GetQueuedCompletionStatus( HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, PULONG_PTR lpCompletionKey, LPOVERLAPPED *lpOverlapped, DWORD dwMilliseconds );
参数解释:
CompletionPort:要从中获取已完成 I/O 操作的 I/O 完成端口句柄。lpNumberOfBytesTransferred:用于接收已传输字节数的指针。如果不关心传输的字节数,可以传递NULL。lpCompletionKey:用于接收已完成 I/O 操作关联的完成键的指针。完成键通常用于标识 I/O 操作的类型或源。lpOverlapped:用于接收指向OVERLAPPED结构的指针,该结构与已完成的 I/O 操作相关联。如果不关心此参数,可以传递NULL。dwMilliseconds:等待时间,以毫秒为单位。如果没有已完成的 I/O 操作,函数将阻塞等待指定的时间。如果传递零,函数将立即返回,如果传递INFINITE,函数将无限期地等待。
返回值:
如果函数调用成功并且获取了已完成的 I/O 操作,它将返回 TRUE。如果函数调用失败或等待超时,它将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。
(3).PostQueuedCompletionStatus函数
PostQueuedCompletionStatus 函数是 Windows API 中用于将已完成的 I/O 操作结果(或其他自定义完成状态)添加到 I/O 完成端口 (I/O Completion Port) 队列的函数。它通常用于多线程编程中,用于将异步操作的结果通知给使用 I/O 完成端口的线程。
函数原型:
BOOL PostQueuedCompletionStatus( HANDLE CompletionPort, DWORD dwNumberOfBytesTransferred, ULONG_PTR dwCompletionKey, LPOVERLAPPED lpOverlapped );
参数解释:
CompletionPort:要将完成状态添加到的 I/O 完成端口句柄。dwNumberOfBytesTransferred:指定已传输的字节数。通常用于通知 I/O 完成端口关于 I/O 操作的结果。dwCompletionKey:指定完成状态的关键字,通常用于标识 I/O 操作的类型或源。lpOverlapped:指向OVERLAPPED结构的指针,通常用于关联已完成的 I/O 操作。可以为NULL。
返回值:
如果函数调用成功,将返回 TRUE。如果函数调用失败,将返回 FALSE。您可以通过调用 GetLastError 获取错误信息。
示例代码:
#include <Windows.h>
#include <stdio.h>
#include <process.h>
HANDLE hCicp = NULL;
unsigned int ThreadFunc(void* arg) {
getchar();
ULONG_PTR key = 10;
OVERLAPPED ol = { 0 };
PostQueuedCompletionStatus(hCicp, 4, 10, &ol);
return 0;
}
int main() {
char userName[256] = { 0 };
HANDLE hFile = CreateFile(TEXT("test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
printf("文件创建失败,错误码:%d\n", GetLastError());
return 1;
}
hCicp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == hCicp) {
printf("创建CreateIoCompletionPort失败,错误码:%d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
ULONG_PTR CK_READ = 0;
CreateIoCompletionPort(hFile, hCicp, CK_READ, 0);
unsigned int uiThreadID = 0;
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadFunc, NULL, 0, &uiThreadID);
OVERLAPPED ol = { 0 };
ReadFile(hFile, userName, 256, NULL, &ol);
DWORD transferedByte = 0;
void* lpContext = NULL;
OVERLAPPED* pOl = NULL;
while (GetQueuedCompletionStatus(hCicp, &transferedByte, (LPDWORD)&lpContext, &pOl, INFINITE)) {
if (lpContext != NULL && 10 == (unsigned long)lpContext) {
printf("IO端口完成退出!\n");
break;
}
printf("%s\n", userName);
}
CloseHandle(hThread);
CloseHandle(hFile);
CloseHandle(hCicp);
return 0;
}

浙公网安备 33010602011771号