Win32编程之异步完成IO(十)
一、文件的异步写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #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; } |
二、文件的异步读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #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; }<br> 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).异步文件写入操作的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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).异步文件读取操作的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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).等待文件写入完毕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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).等待文件读取完毕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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请求,另一个线程对结果进行处理
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #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请求的线程必须对结果进行处理
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #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完成端口的初衷就是与线程池配合使用
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #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 编程。
函数原型:
1 | HANDLE CreateIoCompletionPort(<br> HANDLE FileHandle,<br> HANDLE ExistingCompletionPort,<br> ULONG_PTR CompletionKey,<br> DWORD NumberOfConcurrentThreads<br>); |
参数解释:
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 操作结果。
函数原型:
1 2 3 4 5 6 7 | 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 完成端口的线程。
函数原型:
1 2 3 4 5 6 | 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
获取错误信息。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?