技术演进中的开发沉思-9:window编程系列-内核对象线程同步(下)

今天我们继续走进 Windows 内核的世界,就昨天没说完的内核对象与线程同步内容接着继续,它们就像精密仪器里的齿轮,虽不显眼,却至关重要。

异步设备 I/O

在 Windows 系统中,异步设备 I/O 就像是一场精心编排的接力赛。想象一下,我们的计算机系统是一个庞大的工厂,各个设备(比如硬盘、网卡)就是工厂里忙碌的工人,而应用程序则是负责下订单的客户。当应用程序需要从硬盘读取数据时,如果采用同步 I/O,就好比客户站在工厂门口,眼巴巴地等着工人把货物一件件搬出来,在这个过程中,客户什么都做不了,只能干等。而异步 I/O 则不同,它允许客户下完订单后,不用傻等,继续去做其他事情,工厂(设备)在准备好货物后,会主动通知客户来取。

在 Windows 编程中,使用重叠 I/O(一种异步 I/O 方式)来实现这个过程。下面是一段简单的 VC++ 代码示例,展示如何使用异步 I/O 从文件中读取数据:


#include <windows.h>#include <stdio.h>int main() {HANDLE hFile = CreateFile(TEXT("test.txt"),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);if (hFile == INVALID_HANDLE_VALUE) {printf("Failed to open file. Error: %d\n", GetLastError());return 1;}OVERLAPPED overlapped = { 0 };overlapped.Offset = 0;overlapped.OffsetHigh = 0;overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);DWORD bytesRead;if (!ReadFile(hFile, NULL, 0, &bytesRead, &overlapped)) {if (GetLastError() != ERROR_IO_PENDING) {printf("ReadFile failed. Error: %d\n", GetLastError());CloseHandle(hFile);CloseHandle(overlapped.hEvent);return 1;}}// 可以在等待数据读取完成的过程中做其他事情// 当数据读取完成,事件会被触发WaitForSingleObject(overlapped.hEvent, INFINITE);CloseHandle(hFile);CloseHandle(overlapped.hEvent);return 0;}

在这段代码里,CreateFile函数打开文件时设置了FILE_FLAG_OVERLAPPED标志,开启异步模式。ReadFile函数在数据未准备好时立即返回,我们通过等待overlapped.hEvent事件来得知数据是否读取完成。这样,程序就不会在读取数据时卡住,而是可以高效地利用时间,处理其他任务,就像接力赛中,下一棒选手可以提前做好准备,而不是傻傻地站在原地等待。

二、WaitForInputIdle 函数

WaitForInputIdle函数就像是一位耐心的管家。在 Windows 系统中,当我们启动一个新的进程,比如打开一个应用程序时,这个程序可能需要一些时间来初始化,加载资源、设置窗口布局等等。在这个过程中,如果我们立即对它进行操作,可能会出现混乱,就好比一个刚起床还没收拾好的人,你马上让他去接待客人,肯定会手忙脚乱。

WaitForInputIdle函数的作用就是让我们等待程序完成初始化,准备好接收用户输入后,再进行后续操作。它就像管家在门口守着,告诉我们:“先别着急进去打扰,等里面准备好了,我再通知你。” 以下是一个简单的使用示例:


#include <windows.h>#include <stdio.h>int main() {SHELLEXECUTEINFO ShExecInfo = { 0 };ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;ShExecInfo.hwnd = NULL;ShExecInfo.lpVerb = NULL;ShExecInfo.lpFile = TEXT("notepad.exe");ShExecInfo.lpParameters = NULL;ShExecInfo.lpDirectory = NULL;ShExecInfo.nShow = SW_NORMAL;ShExecInfo.hInstApp = NULL;if (ShellExecuteEx(&ShExecInfo)) {// 等待记事本程序准备好接收用户输入WaitForInputIdle(ShExecInfo.hProcess, INFINITE);printf("Notepad is ready for input.\n");// 可以在这里添加对记事本的操作代码CloseHandle(ShExecInfo.hProcess);} else {printf("Failed to launch Notepad. Error: %d\n", GetLastError());}return 0;}

在这段代码中,我们使用ShellExecuteEx函数启动记事本程序,然后调用WaitForInputIdle函数等待记事本完成初始化。只有当记事本准备就绪,程序才会继续执行后续操作,这就避免了因过早操作而可能引发的问题,让整个过程更加顺畅、有序。

三、MsgWaitForMultipleObjects(ex)函数

MsgWaitForMultipleObjects(ex)函数就像是一个忙碌的交通指挥员,它负责管理多个内核对象和消息队列。在 Windows 系统中,我们的程序可能会创建多个线程,每个线程可能有自己的任务,同时,程序还需要处理各种消息(比如用户的鼠标点击、键盘输入)。这些线程和消息就像道路上川流不息的车辆,如果没有一个好的指挥,很容易造成混乱和拥堵。

MsgWaitForMultipleObjects(ex)函数可以同时等待多个内核对象(比如事件、信号量)变为有信号状态,并且在等待过程中,还能处理消息队列中的消息。它会根据不同的情况,决定是继续等待内核对象,还是先处理消息,就像交通指挥员根据道路情况,灵活地指挥车辆通行,保证整个系统的流畅运行。下面是一个简单的示例代码:


#include <windows.h>#include <stdio.h>DWORD WINAPI ThreadProc(LPVOID lpParameter) {// 模拟线程执行任务for (int i = 0; i < 5; ++i) {printf("Thread is working...\n");Sleep(1000);}// 线程完成任务后设置事件HANDLE hEvent = (HANDLE)lpParameter;SetEvent(hEvent);return 0;}int main() {HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);HANDLE hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)hEvent, 0, NULL);DWORD result = MsgWaitForMultipleObjects(1,&hEvent,FALSE,INFINITE,QS_ALLINPUT);if (result == WAIT_OBJECT_0) {printf("Thread completed its task.\n");} else {printf("Error occurred. Result: %d\n", result);}CloseHandle(hThread);CloseHandle(hEvent);return 0;}

在这个示例中,我们创建了一个线程和一个事件。MsgWaitForMultipleObjects函数等待事件hEvent变为有信号状态,同时在等待过程中,它还会处理消息队列中的消息。当线程完成任务并设置事件后,MsgWaitForMultipleObjects函数就会返回,程序继续执行后续操作,整个过程有条不紊,就像交通指挥员让车辆顺利通过繁忙的路口。

四、WaitForDebugEvent 函数

WaitForDebugEvent函数就像一位严谨的质检员,专门负责监控和调试程序的运行状态。在软件开发过程中,程序难免会出现各种问题,就像生产线上的产品可能会有瑕疵。WaitForDebugEvent函数可以帮助我们捕获程序运行时的各种事件(比如断点命中、异常抛出),就像质检员仔细检查每一个产品,不放过任何一个可能存在的问题。

当我们使用调试器调试程序时,WaitForDebugEvent函数会等待调试事件的发生。一旦有调试事件出现,它就会通知调试器进行相应的处理,比如暂停程序执行、查看变量值等。以下是一个简单的调试示例代码框架:


#include <windows.h>#include <stdio.h>int main() {STARTUPINFO si = { sizeof(si) };PROCESS_INFORMATION pi;if (!CreateProcess(NULL,TEXT("test.exe"),NULL,NULL,FALSE,DEBUG_PROCESS,NULL,NULL,&si,&pi)) {printf("Failed to create process. Error: %d\n", GetLastError());return 1;}DEBUG_EVENT debugEvent;while (WaitForDebugEvent(&debugEvent, INFINITE)) {// 处理调试事件switch (debugEvent.dwDebugEventCode) {case EXCEPTION_DEBUG_EVENT:// 处理异常事件break;case CREATE_PROCESS_DEBUG_EVENT:// 处理进程创建事件break;// 其他类型的调试事件处理default:break;}ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);}CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return 0;}

在这段代码中,我们使用CreateProcess函数以调试模式启动一个程序(这里假设为test.exe),然后通过WaitForDebugEvent函数循环等待调试事件的发生。一旦捕获到调试事件,就根据事件类型进行相应的处理,处理完后使用ContinueDebugEvent函数让程序继续执行。这就像质检员发现产品问题后,进行记录和处理,确保产品质量符合要求,帮助开发者找到并解决程序中的问题。

五、SignalObjectAndWait 函数

SignalObjectAndWait函数就像一位默契的桥梁搭建者,它在两个内核对象之间建立起一种特殊的联系,实现原子操作。想象一下,有两个任务,一个任务完成后需要通知另一个任务开始执行,同时还要确保在通知的过程中,不会出现其他干扰,保证整个过程的原子性(即要么都完成,要么都不完成)。

SignalObjectAndWait函数可以先将一个内核对象(比如事件)设置为有信号状态,然后立即等待另一个内核对象变为有信号状态。在这个过程中,它会确保设置信号和等待信号这两个操作是连续进行的,不会被其他线程打断。以下是一个示例代码:


#include <windows.h>#include <stdio.h>int main() {HANDLE hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);HANDLE hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);// 启动一个线程,等待hEvent1变为有信号状态HANDLE hThread = CreateThread(NULL, 0, [](LPVOID lpParameter) -> DWORD {WaitForSingleObject((HANDLE)lpParameter, INFINITE);printf("Thread received signal and started working.\n");// 线程完成任务后设置hEvent2为有信号状态SetEvent((HANDLE)((DWORD_PTR)lpParameter + 1));return 0;}, (LPVOID)hEvent1, 0, NULL);// 主线程等待一段时间后,使用SignalObjectAndWait函数Sleep(2000);SignalObjectAndWait(hEvent1, hEvent2, INFINITE, FALSE);printf("Main thread completed the operation.\n");CloseHandle(hThread);CloseHandle(hEvent1);CloseHandle(hEvent2);return 0;}

在这个示例中,主线程使用SignalObjectAndWait函数先将hEvent1设置为有信号状态,通知线程开始执行任务,然后等待hEvent2变为有信号状态,即等待线程完成任务。整个过程通过SignalObjectAndWait函数实现了任务之间的有序协作,就像桥梁搭建者在两个地点之间建起一座稳固的桥梁,让任务的传递和执行更加顺畅、可靠。

六、使用等待链遍历 API 来检测死锁

在多线程编程中,死锁是一个令人头疼的问题,就像道路上车辆相互卡住,谁也动不了,导致整个系统陷入僵局。而使用等待链遍历 API 来检测死锁,就像一位敏锐的故障侦探,能够及时发现这些潜在的问题。

死锁通常发生在多个线程互相等待对方释放资源的情况下。等待链遍历 API 可以帮助我们检查线程之间的等待关系,通过分析等待链,找出是否存在循环等待的情况,从而判断是否发生了死锁。下面是一个简单的死锁检测示例代码框架(实际应用中会更复杂):


#include <windows.h>#include <stdio.h>// 模拟两个线程竞争资源可能导致死锁的情况DWORD WINAPI Thread1Proc(LPVOID lpParameter) {HANDLE hMutex1 = (HANDLE)((DWORD_PTR)lpParameter);HANDLE hMutex2 = (HANDLE)((DWORD_PTR)lpParameter + 1);WaitForSingleObject(hMutex1, INFINITE);Sleep(1000);WaitForSingleObject(hMutex2, INFINITE);ReleaseMutex(hMutex2);ReleaseMutex(hMutex1);return 0;}DWORD WINAPI Thread2Proc(LPVOID lpParameter) {HANDLE hMutex1 = (HANDLE)((DWORD_PTR)lpParameter);HANDLE hMutex2 = (HANDLE)((DWORD_PTR)lpParameter + 1);WaitForSingleObject(hMutex2, INFINITE);Sleep(1000);WaitForSingleObject(hMutex1, INFINITE);ReleaseMutex(hMutex1);ReleaseMutex(hMutex2);return 0;}int main() {HANDLE hMutex1 = CreateMutex(NULL, FALSE, NULL);HANDLE hMutex2 = CreateMutex(NULL, FALSE, NULL);HANDLE hThread1 = CreateThread(NULL, 0, Thread1Proc, (LPVOID)hMutex1, 0, NULL);HANDLE hThread2 = CreateThread(NULL, 0, Thread2Proc, (LPVOID)hMutex1, 0, NULL);// 模拟等待一段时间后进行死锁检测Sleep(3000);// 这里可以使用等待链遍历API进行死锁检测,实际代码会更复杂// 为简化说明,暂不展开具体检测代码CloseHandle(hThread1);CloseHandle(hThread2);CloseHandle(hMutex1);CloseHandle(hMutex2);return 0;}

在这个示例中,两个线程Thread1Proc和Thread2Proc以不同的顺序获取互斥锁hMutex1和hMutex2,很可能会导致死锁。在实际应用中,我们可以使用等待链遍历 API 来检测线程之间的等待关系,一旦发现存在循环等待的情况,就可以判断发生了死锁,并及时采取措施进行处理,就像侦探发现案件线索后,迅速展开调查并解决问题,保证系统的正常运行。

最后小结:

在我眼里,异步设备 I/O 如接力赛,通过重叠 I/O 实现异步读取,让程序在等待数据时能处理其他任务;WaitForInputIdle函数像耐心管家,确保新启动程序完成初始化后再接收操作,避免混乱;MsgWaitForMultipleObjects(ex)函数是忙碌的交通指挥员,兼顾多个内核对象与消息队列,维持系统运行秩序 。​

WaitForDebugEvent函数如同严谨质检员,在程序调试时捕获各类事件,助力开发者定位问题;SignalObjectAndWait函数是默契的桥梁搭建者,实现内核对象间原子操作,保障任务有序协作;等待链遍历 API 则像敏锐的故障侦探,通过分析线程等待关系检测死锁,保障系统稳定。今天的内容就到这里吧!下一节,我们将梳理一下windows中很重要I/O相关的问题,未完待续.........

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/910155.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用AI从0开始量化交易-Anaconda环境(env)和缓存(pkg)更改储存位置

之前介绍了Anaconda的安装和环境建立&#xff0c;最近自己的量化交易工具开发的差不多了&#xff0c;却发生了尴尬的问题&#xff0c;C盘被不断增大的conda环境和缓存占据得快满了。 在网上找了些教程&#xff0c;大多是讲迁移的&#xff0c;专门讲改本地改储存位置的比较少&am…

Python爬虫工作基本流程及urllib模块详解

在2025年的数据驱动时代&#xff0c;网络数据成为企业与个人的“金矿”&#xff0c;而Python爬虫则是挖掘这金矿的“利器”&#xff01;无论是抓取电商价格、分析社交媒体趋势&#xff0c;还是构建知识库&#xff0c;Python爬虫都能让你事半功倍。然而&#xff0c;爬虫开发并非…

thinkphp8 模型-一对一,一对多,多对多 学习

thinkphp 命令创建模型&#xff08;和laravel基本一样&#xff09; php think make:model User 在模型里创建字段 protected $table User; protected $pk id; // 定义返回哪些字段 protected $field [id, name]; // 返回字段的类型 protected $schema [id > int] 模…

非线性方程组求解:复杂情况下的数值方法

在科学研究和工程应用中&#xff0c;非线性方程组的求解是一个常见的挑战。尤其当方程组包含复杂函数&#xff08;如特殊函数、积分、微分等&#xff09;&#xff0c;使得雅可比矩阵难以解析求导时&#xff0c;传统的基于解析雅可比矩阵的 Newton-Raphson 方法难以直接应用。本…

边缘计算网关EG8200Mini首发开箱视频丨破解工业互联“协议孤岛”,重塑数据价值核心引擎行业痛点直击|低代码开发

数据采集4G边缘计算网关plc 工业现场设备品牌林立&#xff08;西门子、三菱、欧姆龙等30品牌PLC&#xff09;、协议碎片化&#xff08;Modbus/OPC UA/BACnet等&#xff09;、网络环境复杂&#xff08;户外无光纤、车间电磁干扰&#xff09;——传统网关难以实现多源异构设备统一…

2024-2025下期《网络设备与配置》期末模拟测试

一、 单选题(每题2分&#xff0c;共60分) RIP协议的默认最大跳数是&#xff08; &#xff09; A. 10 B. 15 C. 20 D. 30以下哪个命令可以用来在交换机上进入全局配置模式&#xff1f;&#xff08; &#xff09; A. 使用enable命令 B. 使用configure terminal命令 C. 使用inte…

虹科案例 | 欣旺达如何实现动力电池测试的长期稳定性+自动化?

新能源汽车产业狂飙突进&#xff0c;动力电池测试正面临前所未有的技术大考。 传统电池测试方案常因数据丢帧、协议适配等问题&#xff0c;导致测试周期延长和交付延期。在这场关乎安全与效率的产业竞速中&#xff0c;高精度数据采集与全球化交付能力&#xff0c;已成为动力电…

第17天:数据库学习笔记1

数据库学习笔记 1 SQL语言介绍 2 数据库的安装 2.1 启动数据库 方式一&#xff1a;net start mysql 方式二&#xff1a;在计算机管理里面手动打开数据库 2.2 登录MySQL 方式一&#xff1a;本地登录 即数据库与客户端在同一台电脑上。 方式二&#xff1a;远程登录 mysq…

ChromaDB完全指南:从核心原理到RAG实战

一、引言:拥抱AI时代的“记忆”变革 在人工智能(AI)浪潮席卷全球的今天,大型语言模型(LLM)以其强大的自然语言处理能力,正在重塑我们与信息的交互方式。然而,LLM并非万能,它们普遍存在知识截止日期、无法访问私有数据等“记忆”短板。为了突破这一瓶颈,向量数据库应…

XCUITest + Swift 详细示例

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】

Spring Boot + MyBatis + Redis Vue3 Docker + Kubernetes + Nginx

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 1.1 毕设项目需求分析&#xff08;附需求文档片段&#xff09; 一、项目全景与技术选型 1.1 毕设项目需求分析&#xff08;附需…

【云计算领域数学基础】组合数学优化

一、组合数学优化 1.1、定义与本质特征 1.1.1、组合数学优化的核心原理 ​问题本质与数学工具​ ​组合爆炸问题​&#xff1a;软件输入参数、路径组合随规模指数级增长&#xff0c;如10个二值参数需1024个用例。组合数学通过覆盖数组&#xff08;Covering Array&#xff09;、…

企业文档如何变身AI语料库?无忧文档NLP+OCR技术实战解析

当企业争相采购ChatGPT、文心一言等通用大模型时&#xff0c;却忽略了&#xff1a;企业文档其实是这座数字油田的核心资产。从产品手册、客户案例到会议纪要&#xff0c;企业沉淀的海量文档&#xff0c;这些看似零散的信息&#xff0c;其实正通过AI技术被转化为可复用的“语料库…

掌握Python编程的核心能力,能快速读懂并上手项目开发。

掌握Python编程的核心能力&#xff0c;能快速读懂并上手项目开发。 一套系统且通俗的讲解&#xff0c;理论讲解 实战技巧 代码框架模板&#xff0c;让你能&#xff1a; 看懂Python项目结构 能自己写代码&#xff1a;函数、流程控制、类和模块 能写出一个完整、规范的Pytho…

「Linux文件及目录管理」硬链接与软连接

知识点解析 在Linux系统中,硬链接(Hard Link)和软链接(Symbolic Link,又称软连接)是两种不同的文件链接方式: 1.硬链接(Hard Link): 本质:硬链接是文件的一个别名,与原文件共享相同的inode和磁盘数据块。特点: 数据共享:硬链接与原文件指向同一数据块,修改任…

分清display三个属性

display 三兄弟行为对比表格 属性值是否换行能否设置宽高默认宽度常用标签典型用途block是可以撑满父容器<div>, <p>, <section>页面结构、布局容器inline否不行随内容大小<span>, <a>文字中嵌套、小图标inline-block否可以随内容大小<img&g…

《棒球青训》打造几个国家级运动基地·棒球1号位

Youth Baseball/Softball Base Development Plan | 青少年棒垒球基地建设方案 Core Strategies | 核心战略 Regional Hub Construction | 区域枢纽建设 优先在 长三角/珠三角/成渝经济圈 建设 3大示范性基地 每个基地包含&#xff1a; ▶️ 国际标准青少年赛场&#xff08;…

JavaScript Symbol 属性详解

一、Symbol 的本质与基础 1. Symbol 是什么 JavaScript 的第七种原始数据类型&#xff08;ES6 引入&#xff09;创建唯一的、不可变的标识符主要用途&#xff1a;作为对象的属性键&#xff08;Symbol 属性&#xff09; // 创建 Symbol const id Symbol(id); // id 是描述符…

使用 INFINI Console 全面管理 Elasticsearch 9.X

1、引言 在搜索和分析领域&#xff0c;保持与最新版本的 Elasticsearch 同步对于利用新功能、提升性能和增强安全性至关重要。 Elasticsearch 9.X 作为 Elastic Stack 的最新版本&#xff0c;引入了多项改进&#xff0c;例如更高效的二进制量化和对 ColPali、ColBERT 等模型的支…

开疆智能ModbusTCP转EtherCAT网关连接IVO编码器配置案例

本案例是使用ModbusPOLL软件通过开疆智能ModbusTCP转EtherCAT网关连接编码器的配置案例。具体操作步骤如下 配置过程 打开网关配置软件“EtherCAT Manager”并新建项目选择TCP2ECAT 设置网关的ModbusTCP一侧的IP地址&#xff0c;要与主站软件的组态配置保持一致。 添加松下伺服…