问题描述

进程A给进程B发送数据,进程B收到数据后进行处理。但是奇怪的时跨天的情况下,会偶尔出现进程B收不到数据的情况,重启进程A和进程B后收数据正常(进程B管理着进程A,会一起重启,因此不清楚具体那个软件有问题)。

问题分析和定位

假设:

1. 发数据有问题

2. 收数据有问题

3. 数据处理有问题

通过日志观察,发现进程B收到的最后一帧数据是GPS数据,因此排查GPS数据处理逻辑。发现延时函数存在bug,导致了跨天时概率性的死循环,进而该线程无法继续收数据

延时函数实现

延时函数的代码如下所示(之前在网上抄来的,已经使用了多年了,哭死)

//延时功能2
void sleep2(unsigned int msec){
    //currnentTime 返回当前时间
    QTime n = QTime::currentTime();
    QTime now;
    do
    {
        //currnentTime 返回当前时间
        now = QTime::currentTime();
    }while(n.msecsTo(now)<msec);
}

这段代码为实现延时功能提供了一种简单的解决方案。

它使用了一个无限循环,以不断检查当前时间和起始时间之间的差距是否达到指定的毫秒数。在函数开始时,通过 QTime::currentTime() 获取当前时间,并将其存储在一个变量 n 中。随后,继续做循环,每次在循环内部重新获取当前时间,并计算出两个时间之间的毫秒数差异,然后判断是否已经达到要求的总延迟时间。

如果条件满足,即当前时间与起始时间之差大于等于指定的毫秒数,就会退出循环并继续执行下一步操作;否则,就会一直保持在循环中。这里采用了 do-while 循环来确保至少运行一次循环体。

然后我们写一段测试代码,看看该循环是否能成功退出:

//sleep2跨天测试
void testSleep2(){
    qDebug()<<"----------testSleep2 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    qDebug()<<"current Time:"<<current.toString("hh:mm:ss.zzz");

    //10ms以后
    QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");
    qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //250ms以后
     currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");
    qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //一秒以后
    currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");
    qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);

    qDebug()<<"----------testSleep2 finish--------";
}

代码首先定义了一个起始时间,即23:59:59.900,然后经过一些时间跨度(10毫秒、250毫秒和1秒),计算出新的时间并打印输出。对于每个结果,它还将调用 current.msecsTo(currentNew) 来计算两个时间之间的毫秒数差值,并检查是否小于500毫秒。如果结果小于500毫秒,程序就会中断执行。

这个测试旨在检查 msecsTo() 方法是否能够正常处理时间跨度。换句话说,它测试 msecsTo() 在涉及跨越一天的时间差时是否可以产生正确的结果。测试中使用的时间戳非常接近午夜,以便确保可以生成正确的测试条件。

输出结果:

----------testSleep2 start--------
current Time: "23:59:59.900"
10ms later currentNew: "23:59:59.910"
10ms later break res: false
250ms later currentNew: "00:00:00.150"
250ms later break res: false
1s later currentNew: "00:00:00.900"
1s later break res: false
----------testSleep2 finish--------

结论:过了零点以后,msecsTo()返回的是一个负数,所以导致了死循环。

延时函数另一种实现

网上常见的还有一种写法,代码如下。它能不能通过测试呢?我又试了试。

//延时功能1
void sleep1(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QTime reachTime = QTime::currentTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QTime::currentTime()<reachTime){
        //如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,
        //以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。
        QApplication::processEvents(QEventLoop::AllEvents,100);
    }
}

它的实现方式是计算出当前时间加上需要延时的毫秒数后得到的一个延时结束时间点,然后使用一个 while 循环来不断比对当前时间是否早于该结束时间点。

在循环内部,使用 QApplication::processEvents() 方法强制进行事件处理,以确保程序仍然处于响应状态。这个方法会让 Qt 应用程序类执行默认的处理,并检查是否有新的事件需要处理。如果没有,则等待指定的毫秒数(这里是100毫秒),然后再次检查。如果有新的事件,则立即处理,并返回到循环继续比对时间。

循环会一直运行,直到当前时间晚于或等于设定的延时结束时间点,此时就会跳出循环并继续执行后面的语句。

然后我们同样写一段测试代码,看看该循环是否能成功退出:

//sleep1跨天测试
void testSleep1(){
    qDebug()<<"----------testSleep1 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    QTime reachTime = current.addMSecs(500); //假设延时500ms
    qDebug()<<"reachTime:"<<reachTime.toString("hh:mm:ss.zzz");

    //10ms以后
    QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");
    qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);


    //250ms以后
    currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");
    qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);

    //1s以后
    currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");
    qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"1s later break res:"<<!(currentNew<reachTime);

    qDebug()<<"----------testSleep1 finish--------";
}

代码设计与上面相似。输出结果如下:

---------testSleep1 start--------
reachTime: "00:00:00.400"
10ms later currentNew: "23:59:59.910"
10ms later break res: true
250ms later currentNew: "00:00:00.150"
250ms later break res: false
1s later currentNew: "00:00:00.900"
1s later break res: true
----------testSleep1 finish--------

结论:跨天可以正常跳出循环,不会出现死循环。但是结束时间是下一天情况下,并不能起到循环的作用,10ms的时候就以及跳出循环了。

可取的方法

很容易想到,上面两个函数之所以异常,直接原因是跨天的情况下丢失了日期的信息。解决方法也很简单,将QTime换成QDateTime就可以解决问题。

代码如下:

//延时功能3
void sleep3(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QDateTime reachTime = QDateTime::currentDateTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QDateTime::currentDateTime()<reachTime){
        //如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,
        //以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。
        QApplication::processEvents(QEventLoop::AllEvents,100);
    }
}

//延时功能4
void sleep4(unsigned int msec){
    //currnentTime 返回当前时间
    QDateTime n = QDateTime::currentDateTime();
    QDateTime now;
    do
    {
        //currnentTime 返回当前时间
        now = QDateTime::currentDateTime();
    }while(n.msecsTo(now)<msec);//currentDateTime 返回当前时间
}

测试代码如下:

//sleep3跨天测试
void testSleep3()
{
    QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";

    qDebug()<<"----------testSleep3 start--------";
    QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);
    QDateTime reachTime = current.addMSecs(500);;
    qDebug()<<"reachTime:"<<reachTime.toString(dateTimeFormat);

    //10ms后
    QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);
    qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);

    //250ms后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);
    qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);

    //1s后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);
    qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"1s later break res:"<<!(currentNew<reachTime);

    qDebug()<<"----------testSleep3 finish--------";
}

//sleep4跨天测试
void testSleep4(){

    QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";

    qDebug()<<"----------testSleep4 start--------";

    QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);
    qDebug()<<"current Time:"<<current.toString(dateTimeFormat);

    //10ms以后
    QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);
    qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //250ms以后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);
    qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //一秒以后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);
    qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);


    qDebug()<<"----------testSleep4 finish--------";
}

输出如下:
----------testSleep3 start--------
reachTime: "2023-06-08 00:00:00.400"
10ms later currentNew: "2023-06-07 23:59:59.910"
10ms later break res: false
250ms later currentNew: "2023-06-08 00:00:00.150"
250ms later break res: false
1s later currentNew: "2023-06-08 00:00:00.900"
1s later break res: true
----------testSleep3 finish--------


----------testSleep4 start--------
current Time: "2023-06-07 23:59:59.900"
10ms later currentNew: "2023-06-07 23:59:59.910"
10ms later break res: false
250ms later currentNew: "2023-06-08 00:00:00.150"
250ms later break res: false
1s later currentNew: "2023-06-08 00:00:00.900"
1s later break res: true
----------testSleep4 finish--------

仍然存在的问题

虽然这种方法可以有效实现延时功能,但是在大量延时的情况下会导致 CPU 飙升,从而影响程序的性能和稳定性。建议程序中尽量避免使用这种延时方式,并考虑使用 QTimer 等 Qt 的定时器功能代替。

其他实现方法(我没用过)

QTimer和QEventLoop结合,但是精度不一定能达到1ms。

void sleep(unsigned int msec)
{
    QEventLoop loop;
    QTimer::singleShot(msec, &loop, &QEventLoop::quit);
    loop.exec();
}

另一种是基于QElapsedTimer。这种方式是比较常规的实现方式,尤其适用于需要更高精度的场景,它通过 Qt 的事件循环机制来实现延迟操作,并支持处理界面事件等更加复杂的交互操作。在实际中需要根据场景选择合适的实现方式。

void sleep(unsigned int msec)
{
    QElapsedTimer timer;
    timer.start();
    while (timer.elapsed() < msec) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
}

这个函数创建了一个 QElapsedTimer 对象,用于计算时间间隔。然后利用一个循环不断处理事件,并检查 QElapsedTimer 对象的经过时间是否已经超过了指定的延时时间。

需要注意的是,为了避免因为某些原因导致事件处理停滞,这里将 QCoreApplication::processEvents 的参数设置成了 QEventLoop::AllEvents,以确保能够处理所有事件(包括渲染、resize 等)。同时在每次循环中都限制了最大等待时间为 100 毫秒,以避免阻塞太长时间。

结束

最后不得不说一下,循环是个危险的操作,要多加注意。

完整代码如下:

#include "mainwindow.h"

#include <QApplication>
#include <QCoreApplication>
#include <QTime>
#include <QDateTime>
#include <QDebug>

//延时功能1
void sleep1(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QTime reachTime = QTime::currentTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QTime::currentTime()<reachTime){
        //如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,
        //以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。
        QApplication::processEvents(QEventLoop::AllEvents,100);
    }
}

//sleep1跨天测试
void testSleep1(){
    qDebug()<<"----------testSleep1 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    QTime reachTime = current.addMSecs(500); //假设延时500ms
    qDebug()<<"reachTime:"<<reachTime.toString("hh:mm:ss.zzz");

    //10ms以后
    QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");
    qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);


    //250ms以后
    currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");
    qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);

    //1s以后
    currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");
    qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"1s later break res:"<<!(currentNew<reachTime);

    qDebug()<<"----------testSleep1 finish--------";
}

//延时功能2
void sleep2(unsigned int msec){
    //currnentTime 返回当前时间
    QTime n = QTime::currentTime();
    QTime now;
    do
    {
        //currnentTime 返回当前时间
        now = QTime::currentTime();
    }while(n.msecsTo(now)<msec);
}

//sleep2跨天测试
void testSleep2(){
    qDebug()<<"----------testSleep2 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    qDebug()<<"current Time:"<<current.toString("hh:mm:ss.zzz");

    //10ms以后
    QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");
    qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //250ms以后
     currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");
    qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //一秒以后
    currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");
    qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");
    qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);

    qDebug()<<"----------testSleep2 finish--------";
}


//延时功能3
void sleep3(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QDateTime reachTime = QDateTime::currentDateTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QDateTime::currentDateTime()<reachTime){
        //如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,
        //以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。
        QApplication::processEvents(QEventLoop::AllEvents,100);
    }
}

//sleep3跨天测试
void testSleep3()
{
    QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";

    qDebug()<<"----------testSleep3 start--------";
    QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);
    QDateTime reachTime = current.addMSecs(500);;
    qDebug()<<"reachTime:"<<reachTime.toString(dateTimeFormat);

    //10ms后
    QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);
    qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);

    //250ms后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);
    qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);

    //1s后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);
    qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"1s later break res:"<<!(currentNew<reachTime);

    qDebug()<<"----------testSleep3 finish--------";
}

//延时功能4
void sleep4(unsigned int msec){
    //currnentTime 返回当前时间
    QDateTime n = QDateTime::currentDateTime();
    QDateTime now;
    do
    {
        //currnentTime 返回当前时间
        now = QDateTime::currentDateTime();
    }while(n.msecsTo(now)<msec);//currentDateTime 返回当前时间
}

//sleep4跨天测试
void testSleep4(){

    QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";

    qDebug()<<"----------testSleep4 start--------";

    QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);
    qDebug()<<"current Time:"<<current.toString(dateTimeFormat);

    //10ms以后
    QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);
    qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //250ms以后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);
    qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);

    //一秒以后
    currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);
    qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);
    qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);


    qDebug()<<"----------testSleep4 finish--------";
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    testSleep1();
    testSleep2();
    testSleep3();
    testSleep4();


    MainWindow w;
    w.show();
    return a.exec();
}

#if 0
----------testSleep1 start--------
reachTime: "00:00:00.400"
10ms currentNew: "00:00:00.150"
break res: true
currentNew: "00:00:00.900"
break res: false
----------testSleep1 finish--------
#endif
06-07 15:56