两个数据库互锁,用全局变量互锁会偶发软件卡死

线程1:调用函数的部分代码

theApp.m_cmux = 1;

p->pView->PostMessage(WM_REFRESHSAMPLE, RF_SAMPLE, LPARAM§);

while (theApp.m_cmux)

{

Sleep(20);

}

theApp.m_cmux = 1;

p->pView->PostMessage(WM_REFRESHSAMPLE, RF_SAMPLE, LPARAM§);

其中PostMessage用于发送消息执行数据库写数据,写完数据,最后theApp.m_cmux = 0;

具体实现函数是:

int CSampleTrans::refreshRst(void)

{

CString m_strID;

if (refreshID)

{

cout << "更新结果:开始写入数据库 " << endl;

try

{}

theApp.m_cmux = 0;

cout << "更新结果:写入数据库完成1 " << endl;

return 1;

}

程序还有一个函数,线程2,其部分实现如下:

UINT CSampleTrans::XSYThread(LPVOID pParam)

{

CSampleTrans* p = (CSampleTrans*)pParam;

CString strr, str;

while (theApp.m_cmux)

{

Sleep(20);

}

p->pView->PostMessage(WM_REFRESHSAMPLE, AD_Sample, LPARAM§);//添加样本

}

PostMessage也是通过发送消息操作数据库,具体实现函数是:

long CCVF::addNewPt(CSampleProc *psp, int codeNum)

{

cout << "添加样本:开始写入数据库 " << endl;

long mainID(0);

while (theApp.m_cmux)

{

Sleep(20);

}

theApp.m_cmux = 1;//和检测完成信号互斥

// long m_rID = 0;

CString m_zkIDstr, m_IDstr;

CString str1, str2;

try

{}

theApp.m_cmux = 0;

cout << "添加样本:写入数据库完成 " << endl;

return mainID;

}

程序漏发连续执行150多样本时,卡死在addNewPt函数的 while (theApp.m_cmux)

{

Sleep(20);

}

以下是ai给的解释

原子变量(std::atomic)通俗解释

原子变量是C++11及以上标准提供的线程安全的基础数据类型 ,核心含义是:对该变量的「读取、修改、写入」等所有操作,都会被封装成「不可被中断的单步底层指令」------简单说,一个线程对原子变量执行操作时,要么完整执行完整个操作,要么完全没执行,中间不会被其他线程打断,从根本上避免多线程操作同一变量时的指令交错问题。

可以用一个通俗比喻理解:

  • 普通全局变量(如原代码的int m_cmux:操作像"拆分成三步做事"(比如先看变量值→再修改→最后写回),多线程同时操作时,CPU可能在"三步中间"切换线程,导致操作混乱(这就是你之前卡死的根源);
  • 原子变量(std::atomic<int> m_cmux:操作像"一步做完一件事",CPU一旦开始执行对它的操作,就会一口气完成,中途不会切到其他线程,多线程操作时永远不会出现"半截操作"的情况。

结合你的代码:原子变量解决了什么核心问题

你之前程序卡死的根源,是普通int m_cmux非原子操作特性 ------对m_cmux的赋值(=1/=0)、判断(while(m_cmux)),底层会拆分成「读取变量值→执行逻辑→写回新值」多步指令,高频率操作时线程切换会导致这些指令交错,最终让m_cmux被永久置1。

而改成std::atomic<int> m_cmux后,你代码中所有对m_cmux的操作都会变成原子操作,举两个核心场景:

1. 赋值操作:theApp.m_cmux = 1; / theApp.m_cmux = 0;
  • 普通int:底层分3步(读当前m_cmux值→计算新值1/0→写回内存),中途可能被其他线程打断;
  • 原子变量:3步合并为1步不可中断指令 ,要么成功把m_cmux改成1/0,要么没改,不会出现"改到一半被切线程"的情况。
2. 判断操作:while (theApp.m_cmux)
  • 普通int:每次循环都会"读取m_cmux值→判断是否非0",两步操作中间可能被其他线程修改m_cmux,导致判断结果失真;
  • 原子变量:"读取+判断"的核心读取操作是原子的,读取到的m_cmux值一定是某个线程操作完成后的真实值,不会读到"半截修改"的无效值。

原子变量的3个关键特性(贴合你的使用场景,无冗余)

  1. 语法完全兼容普通变量 :你原有代码中所有对m_cmux的操作(=1/=0/while判断)无需做任何修改,直接复用即可,这也是为什么说"最小改动解决卡死问题";
  2. 无需额外加锁 :原子变量的线程安全是底层硬件指令级别的保障,不需要手动加临界区、互斥量,比传统"全局变量+锁"的方式更轻量、效率更高;
  3. 仅保证自身操作原子性 :原子变量只确保对自身 的单个操作不可中断,若需要多个操作的原子性(比如"判断m_cmux=0后立即置1"),仍需配合锁,但你的代码中无此场景,直接用原子变量即可满足需求。

为什么原子变量能彻底解决你之前的卡死问题

你之前的卡死,是高频率操作下普通m_cmux的非原子指令交错 导致的致命时序:UI线程执行m_cmux=1后被临时挂起,后续的m_cmux=0没机会执行,最终m_cmux永久置1。

改成原子变量后,m_cmux=1的赋值是一步不可中断 的,且所有线程对m_cmux的读取都是真实值:

  • 要么UI线程完整执行m_cmux=1,后续只要执行到写库完成的逻辑,就一定会完整执行m_cmux=0
  • 要么UI线程没执行m_cmux=1,其他线程读到的m_cmux始终是0,不会出现"半截赋值导致永久置1"的情况;
    从根源上杜绝了m_cmux被永久置1的可能,自然也就不会出现addNewPt循环无限等待、UI线程阻塞卡死的问题。

简单总结

  1. 原子变量的核心:单个操作不可被中断,多线程操作时无"半截操作",保证变量值的真实、一致;
  2. 对你的代码:完全兼容原有逻辑,1行代码修改,就能解决普通变量非原子性导致的指令交错、值失真、永久置1等问题;
  3. 实际效果:连续执行150+甚至更多样本时,无论线程切换多频繁,对m_cmux的操作都不会混乱,程序不会再因该问题卡死。
相关推荐
大空大地20262 小时前
流程控制语句--if语句
开发语言
霖霖总总2 小时前
[小技巧64]深入解析 MySQL InnoDB 的 Checkpoint 机制:原理、类型与调优
数据库·mysql
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于PHP的发热病人管理平台的设计与实现为例,包含答辩的问题和答案
开发语言·php
Trouvaille ~2 小时前
【Linux】UDP Socket编程实战(一):Echo Server从零到一
linux·运维·服务器·网络·c++·websocket·udp
HellowAmy2 小时前
我的C++规范 - 线程池
开发语言·c++·代码规范
独自破碎E2 小时前
【BISHI9】田忌赛马
android·java·开发语言
czy87874752 小时前
const 在 C/C++ 中的全面用法(C/C++ 差异+核心场景+实战示例)
c语言·开发语言·c++
十五年专注C++开发2 小时前
MinHook:Windows 平台下轻量级、高性能的钩子库
c++·windows·钩子技术·minhook
范纹杉想快点毕业2 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc