线程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个关键特性(贴合你的使用场景,无冗余)
- 语法完全兼容普通变量 :你原有代码中所有对
m_cmux的操作(=1/=0/while判断)无需做任何修改,直接复用即可,这也是为什么说"最小改动解决卡死问题"; - 无需额外加锁 :原子变量的线程安全是底层硬件指令级别的保障,不需要手动加临界区、互斥量,比传统"全局变量+锁"的方式更轻量、效率更高;
- 仅保证自身操作原子性 :原子变量只确保对自身 的单个操作不可中断,若需要多个操作的原子性(比如"判断
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行代码修改,就能解决普通变量非原子性导致的指令交错、值失真、永久置1等问题;
- 实际效果:连续执行150+甚至更多样本时,无论线程切换多频繁,对
m_cmux的操作都不会混乱,程序不会再因该问题卡死。