当前位置: 首页 > news >正文

技术演进中的开发沉思-40 MFC系列:多线程协作

今天说说MFC的线程,当年用它实现中间件消息得心应手之时,可以实现一边实时接收数据,一边更新界面图表图文信息,顺滑得让人想吹声口哨。 MFC 多线程它像给程序装上了分身术,让原本只能 “单任务跑腿” 的代码,突然有了 双重任务 的本事。

一、线程的底层逻辑

设计模式里有个工厂模式,在我的眼里,进程就像一整个工厂:有独立的厂房(内存空间)、固定的设备(系统资源),是操作系统能调度的最小单位。而线程就是工厂里的工人—— 他们共享厂房里的工具(进程资源),但各干各的活,既能协作装配一台机器,也能分头处理不同订单。

比如你打开的 VC++6.0 是一个进程,里面敲代码的编辑窗口、实时编译的后台进程、甚至右下角的拼写检查提示,都是不同的线程在工作。线程有自己的优先级:就像工厂里紧急订单优先处理,高优先级线程(比如实时数据刷新)会抢占 CPU 资源,但如果一直让 “急单工人” 霸占设备,其他线程就会饿死 —— 这就是当年调试时总遇到的 “界面假死” 根源。

二、两类线程

MFC 里的线程分两类,就像工厂里的两种工人:

Worker Threads(工作线程) 是埋头干活的 “流水线工人”。他们不碰界面,专门处理后台任务 —— 比如我当年写的串口数据解析、文件批量转换。这类线程没有消息循环,干完活就下班,简单直接。

UI Threads(界面线程) 则是 “前台接待员”。他们有自己的消息循环(就像接待台的呼叫系统),能创建窗口、处理按钮点击等交互。当年做数据监控软件时,我用 UI 线程单独管理报警弹窗,就算主界面卡了,报警窗口仍能弹出来 —— 这是当时在C/S开发场景里可是能救命的设计。

三、CWinThread:MFC 给线程搭的 “脚手架”

MFC 用 CWinThread 类封装了线程操作,就像给工人准备了标准化工具包。创建线程不用直接调用 Windows API,通过它能少写很多重复代码。

比如创建一个工作线程,核心就两步:


// 1. 定义线程函数(工人要干的活)UINT DataProcessThread(LPVOID pParam){// pParam是传递的参数(比如数据指针)DataHandler* handler = (DataHandler*)pParam;while(handler->IsRunning()){handler->ProcessOnePacket(); // 处理一个数据包Sleep(10); // 让出CPU给其他线程}return 0; // 线程结束}// 2. 启动线程(招募工人)CWinThread* pThread = AfxBeginThread(DataProcessThread, // 线程函数&m_dataHandler, // 传递参数THREAD_PRIORITY_NORMAL, // 优先级0, // 栈大小(默认即可)CREATE_SUSPENDED, // 先挂起,准备好再运行NULL);if(pThread != NULL){pThread->m_bAutoDelete = TRUE; // 线程结束后自动释放pThread->ResumeThread(); // 开始工作}

这段代码里的 AfxBeginThread 是 MFC 的 “线程启动器”。当年总忘了设 m_bAutoDelete,结果线程结束后内存没释放,调试时看到内存占用越来越高,才明白 MFC 的 “自动清理” 有多重要。

UI 线程的创建稍复杂些,需要派生 CWinThread 子类,重写 InitInstance 函数初始化窗口。就像给接待员配专属工作台,得先把桌子椅子(窗口资源)准备好。

四、线程之间的协作

线程管理的精髓,在于 “该停的时候停好,该协作的时候不抢”。

早期不理解,写程序时,我粗暴地用 TerminateThread 强制结束线程,结果经常丢数据 —— 这就像突然关掉工厂电源,工人手里的零件肯定会散落一地。正确的做法是 “温柔通知”:用一个全局标志位告诉线程 “可以下班了”。


// 安全结束线程的例子class DataHandler{private:bool m_bRunning; // 线程运行标志public:DataHandler() : m_bRunning(false) {}void Start() { m_bRunning = true; }void Stop() { m_bRunning = false; } // 通知线程停止bool IsRunning() { return m_bRunning; }};

线程同步则是另一个大学问。多个线程抢着读写同一块数据时,就像两个工人抢一把扳手 —— 轻则数据错乱,重则程序崩溃。MFC 提供了 CCriticalSection(临界区)、CMutex(互斥量)等 “工具锁”,我最常用临界区,就像给扳手加个锁,谁用谁开锁,用完再还给别人。


// 用临界区保护共享数据CCriticalSection m_csData; // 定义临界区(锁)vector<DataPacket> m_packets; // 共享数据// 线程1:添加数据void AddPacket(DataPacket pkt){CSingleLock lock(&m_csData, TRUE); // 上锁m_packets.push_back(pkt);} // 离开作用域自动解锁// 线程2:读取数据vector<DataPacket> GetAllPackets(){CSingleLock lock(&m_csData, TRUE); // 上锁vector<DataPacket> temp = m_packets;m_packets.clear();return temp;}

卡过早期做股票行情软件的C++程序,看过K线图的处理情况,行情接收线程和 UI 刷新线程总抢着访问行情数据,用了临界区后,K 线图再也没出现过 “跳空” 的怪象。

最后小结

现在用 Python、Go 时,线程(协程)的用法变了很多,但每次处理并发,总会想起 MFC 多线程的日子。那些调试线程死锁的深夜,盯着调试器里的线程状态发呆;涉世之初时,爷爷不乏为少加一个锁导致程序在客户现场崩溃的愧疚;还有第一次用多线程让界面流畅运行时的兴奋 —— 这些经历比代码本身更珍贵。

MFC 多线程就像编程界的 “老自行车”,现在看来有些笨拙,但它教会我的道理从未过时:好的并发设计,不是让线程各自为战,而是让它们像默契的团队一样协作。就像工厂里的工人,各司其职又互相配合,才能高效运转。未完待续...........

http://www.xdnf.cn/news/1154935.html

相关文章:

  • JavaScript平滑滚动与锚点偏移控制的完整指南
  • InfluxDB 核心概念与发展历程全景解读(二)
  • 18.TaskExecutor获取ResourceManagerGateway
  • Unity笔记——Unity 封装方法指南
  • OpenCV 入门知识:图片展示、摄像头捕获、控制鼠标及其 Trackbar(滑动条)生成!
  • QT无边框窗口
  • 2025 年科技革命时刻表:四大关键节点将如何重塑未来?
  • 详解Mysql Order by排序底层原理
  • RK3588 编译 Android 13 镜像方法
  • 用C语言实现控制台应用的按键方向控制
  • Qt的安装和环境配置
  • 【愚公系列】《MIoT.VC》002-构建基本仿真工作站(布局一个基本工作站)
  • OPC UA, CAN, PROFINET, SOCKET, MODBUS, HTTP, S7七种物联网常用协议解释
  • 金融工程、金融与经济学知识点
  • Claude 3模型深度剖析:架构创新与性能突破
  • JAVA面试宝典 -《容灾设计:异地多活架构实践》
  • 从零搭建智能搜索代理:LangGraph + 实时搜索 + PDF导出完整项目实战
  • 从TPACK到TPACK - AI:人工智能时代教师知识框架的重构与验证
  • Kubernetes中为ELK组件配置持久化存储
  • nginx定期清理日志
  • 线程池的状态
  • AI开发 | 基于FastAPI+React的流式对话
  • sqli-labs通关笔记-第09关 GET时间盲注(单引号闭合 手工注入+脚本注入两种方法)
  • Docker Desktop 入门教程(Windows macOS)
  • Elasticsearch 简化指南:GCP Google Compute Engine
  • 相似度计算
  • COGNEX康耐视IS5403-01智能相机加Navitar 18R00 LR1010WM52镜头
  • IP协议介绍
  • GPT-4o mini TTS:领先的文本转语音技术
  • VTM 是“H.266/VVC 标准的官方参考软件”视频分析,入门教程,它存在的唯一目的就是“让学术界和工业界在同一把尺子上做实验