實際開發中遇到問題然后處理問題是提高能力的最直接方式,筆者的文章都是在實際開發過程中發現問題然后去解決問題的過程,希望對讀者有幫助。
這兩天一直在處理一個程序崩潰的問題,大概的現象是程序跑起來沒多久,就直接崩潰掉了,抓取過dump文件用windbg去查具體的問題,但是沒有任何效果,然后通過自己調試和測試大致知道問題出在什么地方,就是數據入庫的時候導致了程序崩潰。那么就在這個地方下功夫去處理,在插入數據庫的函數中發現開發者用到了QCoreApplication::processEvents()接口,然后就去查了一下這個函數的作用。
QCoreApplication::processEvents()的作用,作用就是處理密集型耗時的事情。
當我把入庫函數中相關QCoreApplication::processEvents()的語句都屏蔽掉的時候。
int JNDBUtil::InsertTableData(QString tableName, const QVariantMap& params) { BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to set sqlquery"); //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); QMap<QString, QVariant>::const_iterator c_iter; QString property = ""; QString values = ""; for (c_iter = params.begin(); c_iter != params.end(); ++c_iter) { property += "`" + c_iter.key() + "`,"; values += (":" + c_iter.key() + ","); //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); } property = " ( " + property.mid(0, property.length() - 1) + " ) "; values = "Values ( " + values.mid(0, values.length() - 1) + " ) "; QString queryStr = "insert into " + tableName + property + values; //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); return DBHandler::InsertTableData(queryStr, params); }
運行程序,程序沒有掛掉,但是程序出現假死的狀態,界面根本無法操作,但是后台數據入庫還是在進行中,這就說明了一點,數據入庫和界面在同一個線程中,處理數據入庫消息的時間太長了,界面沒辦法刷新。
上面也說了,如果我不屏蔽QCoreApplication::processEvents,那么程序在運行20秒左右的時候就會掛掉。
實際上在我們開發的過程中這樣做是很不好的,界面刷新和數據入庫的操作應該要做到分離,不能在同一個線程中去處理,於是我就將數據入庫這部分的操作另外起了一個線程處理,在運行的時候程序就沒有出現崩潰的現場。
InBoundThread.h
#pragma once #include <QThread> #include "jnstruct.h" #include <QMap> #include <QVector> #include <QQueue> class JNDBUtil; class DeviceInfoWidget; class ForRecoderInfoSt; struct FormulaRecordInfo { QString RecordStepNo; //從工步配方表中獲得的工步號 QString RecordTime; //從工步配方表中獲得的記錄時間 QString RecordCutOffSet; //從工步配方表中獲得的截止設置 }; struct ForRecorderInfo { int chan; ForRecoderInfoSt m_recordinfo; }; class CInBoundThd : public QThread { Q_OBJECT public: CInBoundThd(DeviceInfoWidget *pDeviceInfoWgt); ~CInBoundThd(); static void InitOldMapData(); void GetDBFormula(const QString& formulaname, const QString& celltypename); void GetRecorderInfoAndCNo(const ForRecoderInfoSt &st, int iChan); private: void InitMem(); //更新過程數據和截止數據到數據庫 void UpdateDataToDB(const ForRecoderInfoSt &st, int iChan); bool SaveCutOffData(const ForRecoderInfoSt &st, int iChan); bool SaveProcessData(const ForRecoderInfoSt &st, int iChan); protected: void run() override; private: //DeviceInfoWidget *m_pDeviceInfoWgt;//主界面 static QMap<int, ForRecoderInfoSt> g_mapOldStThd; QQueue<ForRecorderInfo> m_TmpFRecordInfo; QVector<FormulaRecordInfo> m_VecFormulaRecordInfo; JNDBUtil *m_pUtil; ForRecoderInfoSt m_TmpRecordInfo; int m_TmpChan; };
InBoundThread.cpp
#include "InBoundThread.h" #include "..\Common/bmslogger.h" #include "..\Common/jndbutil.h" #include "..\Common/globalconststr.h" #include "..\Common/commFuc.h" #include "..\OtherUI/CellBind/cellbindwidget.h" #include <QDateTime> QMap<int, ForRecoderInfoSt> CInBoundThd::g_mapOldStThd; CInBoundThd::CInBoundThd(DeviceInfoWidget *pDeviceInfoWgt) { //m_pDeviceInfoWgt = pDeviceInfoWgt; m_pUtil = new JNDBUtil; InitMem(); } CInBoundThd::~CInBoundThd() { SafeDelete(m_pUtil); } void CInBoundThd::InitOldMapData() { g_mapOldStThd.clear(); for (int i = 0; i < 40; i++) { g_mapOldStThd[i + 1] = ForRecoderInfoSt(); } } void CInBoundThd::GetDBFormula(const QString& formulaname, const QString& celltypename) { m_VecFormulaRecordInfo.clear(); QString sCon = QString("SELECT * FROM work_step_formula WHERE cellType = '%1' AND formulaName = '%2';").arg(celltypename).arg(formulaname); auto list = m_pUtil->GetTableData(sCon); for (auto item : list) { FormulaRecordInfo tmpFormulaInfo; tmpFormulaInfo.RecordStepNo = item[GlobalConstStr::m_gFiled_stepNo].toString(); tmpFormulaInfo.RecordTime = item[GlobalConstStr::m_gFiled_recoderTime].toString(); tmpFormulaInfo.RecordCutOffSet = item[GlobalConstStr::m_gFiled_cutOffSet].toString(); m_VecFormulaRecordInfo.append(tmpFormulaInfo); } } void CInBoundThd::GetRecorderInfoAndCNo(const ForRecoderInfoSt &st, int iChan) { ForRecorderInfo RecordInfo; RecordInfo.chan = iChan; RecordInfo.m_recordinfo = st; //m_TmpFRecordInfo.append(RecordInfo); m_TmpFRecordInfo.enqueue(RecordInfo); } void CInBoundThd::InitMem() { InitOldMapData(); } void CInBoundThd::UpdateDataToDB(const ForRecoderInfoSt &st, int iChan) { try { if (g_mapOldStThd.contains(iChan)) { auto &oldSt = g_mapOldStThd[iChan]; if (m_VecFormulaRecordInfo.size() == 0) { return; } //存入每個配方第一個工步的起始數據 if (st.stepNo == m_VecFormulaRecordInfo[0].RecordStepNo.toUInt() && st.iTime == m_VecFormulaRecordInfo[0].RecordTime.toInt() * 1000) // m_VecFormulaRecordInfo[0].RecordStepNo m_VecFormulaRecordInfo[0].RecordTime.toInt() * 1000 { SaveCutOffData(st, iChan); } if (oldSt.stepNo != 0 && oldSt.stepNo != st.stepNo) { //存入截止數據 SaveCutOffData(oldSt, iChan); if (st.stepNo != 0 && st.stepType != 0) { SaveCutOffData(st, iChan); } } if (st.runMode == JN::EmRunState::emStepStart) { //存入過程數據 BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to store processdata!"); //add by zhurui 2021/5/20 SaveProcessData(st, iChan); } oldSt = st; } } catch (const std::exception&) { BMSLogger::GetInstance().OutputLog(LogLevel::LOG_ERROR, GetCurTime() + QString::fromLocal8Bit("UpdateDataToDB::更新數據到數據庫失敗!!!")); } } bool CInBoundThd::SaveCutOffData(const ForRecoderInfoSt &st, int iChan) { BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to store cutoffdata"); auto &cellInfo = CellBindWidget::GetCellInfo(); QString cellSN = cellInfo.value(iChan); if (cellSN.isEmpty()) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("電芯截止數據記錄失敗,%1通道沒有綁定電芯碼!").arg(iChan)); return false; } int runTime = QDateTime::fromString(st.date, "yyyy-MM-dd hh:mm:ss.zzz").toTime_t(); QString id = QString("%1-%2-%3").arg(iChan).arg(cellSN).arg(runTime); QVariantMap map; map["id"] = id; map["batteryCode"] = cellSN; map["channelNo"] = iChan; map["current"] = FormatNum(st.cur); map["voltage"] = FormatNum(st.vol); map["energy"] = FormatNum(st.power); map["currentTime"] = st.date; //add by zhurui 2021/4/8 map["cutoffstepNo"] = st.stepNo; map["cutoffstepType"] = st.stepType; map["cutoffcap"] = FormatNum(st.cap); if (m_pUtil->InsertTableData("formation_cutoffdata", map) == -1) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("電芯截止數據記錄失敗,%1通道").arg(iChan)); return false; } return true; } bool CInBoundThd::SaveProcessData(const ForRecoderInfoSt &st, int iChan) { auto &cellInfo = CellBindWidget::GetCellInfo(); QString cellSN = cellInfo.value(iChan); if (cellSN.isEmpty()) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("電芯過程數據記錄失敗,%1通道沒有綁定電芯碼!").arg(iChan)); return false; } int runTime = QDateTime::fromString(st.date, "yyyy-MM-dd hh:mm:ss.zzz").toTime_t(); QString id = QString("%1-%2-%3").arg(iChan).arg(cellSN).arg(runTime); QVariantMap map; map["id"] = id; map["batteryCode"] = cellSN; map["channelNo"] = iChan; map["current"] = FormatNum(st.cur); map["voltage"] = FormatNum(st.vol); map["capacity"] = FormatNum(st.cap); map["energy"] = FormatNum(st.power); map["batteryTemperature"] = FormatNum(st.temp); map["ratio"] = FormatNum(st.curRat); map["povl"] = FormatNum(st.outVol); map["stepNo"] = st.stepNo; map["stepType"] = /*m_formationProtocol.GetStepName(*/st.stepType/*)*/; map["sumStep"] = st.accStep; map["loopNo"] = st.loopNo; //map["funcCode"] = st.loopNo;//沒有 map["runState"] = st.runMode; map["runTime"] = st.iTime; map["currentTime"] = st.date; if (m_pUtil->InsertTableData("formation_processdata", map) == -1) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("電芯過程數據記錄失敗,%1通道").arg(iChan)); return false; } return true; } void CInBoundThd::run() { while (true) { if (m_TmpFRecordInfo.length() > 200) { ForRecorderInfo m_RunRecorderInfo; m_RunRecorderInfo = m_TmpFRecordInfo.dequeue(); //UpdateDataToDB(m_TmpRecordInfo, m_TmpChan); UpdateDataToDB(m_RunRecorderInfo.m_recordinfo, m_RunRecorderInfo.chan); } } }
在deviceinfowidget.h中聲明線程
CInBoundThd* m_pInBoundThd;
在deviceinfowidget.cpp中模塊的構造函數中初始化
m_pInBoundThd = new CInBoundThd(this); m_pInBoundThd->start();
析構函數中處理方式
if (m_pInBoundThd != nullptr) { m_pInBoundThd->terminate(); m_pInBoundThd->wait(); delete m_pInBoundThd; }
歡迎各位大佬指正,這其中還有一個問題,就是為什么加上了QCoreApplication::processEvents()以后會掛?需要再去深究一下!
2021年6月1日修改
實際運行中發現一個問題,多線程同步的問題沒有考慮到,就是我們常說的加鎖處理,在本例中有兩個線程對隊列進行了操作,一個地方插入隊列,一個地方取隊列,所以這兩個地方就需要加鎖,如果按照樓主這樣處理程序確實沒有問題,但是隊列中小於200條的記錄
m_devicemutex.lock(); m_TmpFRecordInfo.enqueue(RecordInfo); m_devicemutex.unlock();
void CInBoundThd::run() { while (true) { //msleep(300); m_devicemutex.lock(); if (m_TmpFRecordInfo.size() > 0) { ForRecorderInfo m_RunRecorderInfo; m_RunRecorderInfo = m_TmpFRecordInfo.dequeue(); //UpdateDataToDB(m_TmpRecordInfo, m_TmpChan); m_devicemutex.unlock(); UpdateDataToDB(m_RunRecorderInfo.m_recordinfo, m_RunRecorderInfo.chan); } else { m_devicemutex.unlock(); } } }