Qt多线程通信

3

我是一名新手,对C++和Qt一无所知,在实现一个良好且不复杂的多线程通信环境方面遇到了一些麻烦。

基本上我有3个线程,一个用于GUI,另一个用于处理由连接到PC的USB设备发送的更新,还有一个将处理由device获取的信息并控制 deviceGUI以更改它们的状态。 所以基本上有3个线程:GUIdevicecontrol

我的第一种方法是让device使用通过USB发送的信息填充其私有成员,并具有一些get()方法来转换此数据并返回它(使用互斥锁确保数据仍然有效)。 问题在于当control调用device中的get()方法时,它不会返回任何新信息(我期望这些方法甚至从不返回,因为线程被锁定在另一个方法中,但它们确实返回,而且没有触发get()方法内部的任何断点)。

Qt通常执行跨线程通信的方式是使用Signal 和 Slots,但Signal和Slots的问题是当一个线程正在处理并且具有Slot时,如果发送了某些Signal,则此Slot将永远不会执行。 即使我能够设法使用Signal和Slots触发新数据更新,我担心会有大量的Signal被发送,因为该设备更新非常快,而且我有很多数据类型,并且使用QAtomicInt对许多数据类型将没有用,所以我的一般问题是如何以最佳方式使线程共享数据并仍然保持运行无限进程循环?

我的目标的一个好例子是:

控制线程:

while(true){
    angle = device.getAngle(); //device is a member of control object and is running in a separate thread
    doCalculations(angle);
}

设备线程:

void process(){
while(true)
    usbRead(data, size_of_data);
}

short getAngle(){
    return (data[0] << 8 | data[1]);
}

在这个例子中,我没有放置互斥锁等内容,仅提供基本功能。

如所请求的那样,以下是我如何启动我的线程:

test::test(QWidget *parent) : QMainWindow(parent) , cvControl(device, 0)
{ 
    //ui setup

    connect(&device, SIGNAL(deviceConnected(bool)), this, SLOT(updateStatusConnection(bool)));

    device.moveToThread(&deviceThread);
    cvControl.moveToThread(&controlThread);

    connect(&deviceThread, SIGNAL(started(void)), &device, SLOT(process(void)));   
    connect(&device, SIGNAL(deviceFinished(void)), &deviceThread, SLOT(quit(void)));
    connect(&cvControl, SIGNAL(controlFinished(void)), &controlThread, SLOT(quit(void)));
    connect(&deviceThread, SIGNAL(finished(void)), &device, SLOT(deleteLater(void)));
    connect(&controlThread, SIGNAL(finished(void)), &cvControl, SLOT(deleteLater(void)));

    connect(this, SIGNAL(startControlProcess(void)), &cvControl, SLOT(process(void)));

    deviceThread.start();
    controlThread.start();
}

void test::on_btnRun_clicked()
{
    if(ui.btnRun->text() == "Run")
    {
        ui.btnRun->setText(QString("Stop"));
        disbleControls();
        emit startControlProcess();
    }
    else
    {
        ui.btnRun->setText(QString("Run"));
        enableControls();
        cvControl.abort.store(1);
    }
}

你已经在这里问过了,我们已经回答了:http://stackoverflow.com/questions/20825039/qthread-slots-behavior 请通过引用现有线程等重新表述您的问题。看起来你正在尝试通过几个小线程解决一个任务,而偶尔的读者不知道其他部分。无论如何,那边的答案有什么问题吗?你可以按照所写的执行事件循环,它将安排事件进行处理。此外,我建议在发布每小时或每秒钟的问题之前先阅读一些关于这些事情的内容。 - László Papp
我只是在说,如果我实现一个需要通过消息询问事情的系统,我就必须要等待消息返回我的数据才能睡觉。我想要像这个例子一样更加简洁和快速的东西。 - Michel Feinstein
我真的无法理解你在qt事件循环方面遇到的问题。这感觉像是一种过早的优化,但是如果你坚持的话:另一种选择是睡眠,并定期唤醒进行手动轮询,但是那样你就必须忘记qt信号和槽,而且你将独自面对问题,在睡眠期间将无法正确处理,或者你将自己重新发明qt事件循环,或者可能是某个不太健壮和稳定的版本... - László Papp
QCoreApplication::processEvents()怎么样? - Michel Feinstein
所以你的建议基本上是调用exec()并等待它返回? - Michel Feinstein
显示剩余15条评论
2个回答

2

处理这个问题有多种方法:

1)手动休眠并在一定时间后自行唤醒以检查是否有变化。这被称为轮询。

2)为您的线程使用事件循环来处理信号和槽等事件。

我建议采用后者,因为前者可能存在无法做任何事情而睡眠的缺陷,而且您可能会弄错。更重要的是,您也会突然失去了伟大的信号和槽机制,使您的代码比必要的耦合更紧密。

至于正确执行后者,您需要确保具有适当的Qt事件循环执行,这由后期版本的QThread保证。话虽如此,请阅读以下文档,因为我认为它很有用:

int QThread::exec() [protected]

进入事件循环并等待直到调用exit(),返回传递给exit()的值。如果通过quit()调用exit(),则返回值为0。

此函数应从run()内部调用。必须调用此函数以启动事件处理。

您还可以通过混合上述两种方法来实现中间第三种解决方案:调用下面的方法以明确确保所有事件都得到处理。这将是一种半同步方式(取决于您如何看待它),因为您需要在从轮询睡眠中唤醒时执行该操作,但与其手动处理轮询,不如使用此便利方法。

void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents) [static]

根据指定的标志处理调用线程的所有挂起事件,直到没有更多事件需要处理。

当程序正在执行长时间操作(例如复制文件)时,您可以偶尔调用此函数。

如果您正在运行一个本地循环,该循环连续调用此函数而没有事件循环,则不会处理DeferredDelete事件。这可能会影响小部件的行为,例如依赖DeferredDelete事件正常工作的QToolTip。另一种选择是在该本地循环中从sendPostedEvents()调用。

调用此函数仅处理调用线程的事件。


好的,我对这个实现有一些问题:1-如果我调用QCoreApplication::processEvents()并且有多个信号被发送...如果信号队列永远不会为空(执行Slot所需的时间足以到达其他信号),那么这个调用会发生什么?它永远不会返回吗?2-很抱歉,我无法理解这个exec()调用究竟是做什么的,谁调用它?它是我必须子类化的函数吗?谁调用exit?它是做什么的?很抱歉,但是这份文档没有向我展示如何真正使用它,好的教程示例是非常受欢迎的。 - Michel Feinstein
我阅读了这段内容,但并没有理解exec()的一般行为。我可以看到每个QThread都有一个事件循环,而exec()会启动它,所以如果我正在这个线程中运行,并且在一个线程中只能运行一个方法,那么调用exec()会停止我的当前process()方法吗?而且我没有子类化run(),那么是谁调用run()?这就是我要求示例的原因,事物的一般行为对我来说并不清楚。 - Michel Feinstein
我不打算实现自己的逻辑,我想使用Qt框架来完成我的工作。我没有做任何奇怪的事情,所以我相信我的exec()运行得很好...但是由于process()没有返回,所以槽永远不会被执行,对吗? - Michel Feinstein
我现在理解了你的一般性回答,你的意思是说对于一切都要使用信号和槽函数,而不是使用process()无限循环,只需让槽函数在需要处理某些内容时被调用即可...这很好...但是难道没有其他方法吗?就像我在问题中所说的那样,我想避免这种情况,我希望有更简单的方式来实现我的小例子,只要在需要这个数据时调用一个方法返回一个数据,而不是每当有新数据可用时就调用一个槽函数。 - Michel Feinstein
抱歉,我刚看到这个问题已经关闭了,但我没有看到您仍然需要标记一个答案,即使在关闭的问题中...这是我的错误。 - Michel Feinstein
显示剩余2条评论

1
假设您的设备工作方式如下:
while(keep_running){
    handle_incoming_data();
}

在这种情况下,您没有时间处理此线程中的任何其他事情,因为此循环根本不会退出。但是,您可以将循环更改为以下结构:
public slots:
    void single_step(){
        if(keep_running){
            handle_incoming_data();
            QTimer::singleShot(0, this, SLOT(single_step()));
        }
    }

或者

public slots:
    void start_work(){
        my_timer->start(0);
    }

    void stop_work(){
        my_timer->stop();
    }

在这种情况下,my_timer是一个QTimer*并连接到正确的槽(此处为handle_incoming_data())。请注意,0毫秒的超时具有特殊含义:

As a special case, a QTimer with a timeout of 0 will time out as soon as all the events in the window system's event queue have been processed. This can be used to do heavy work while providing a snappy user interface:

QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(processOneThing()));
timer->start();

欲了解更多信息,请参阅有关 QTimer部分


Laszlo,我从来没有说过“我不想要它”,我只是说我对它一无所知,这是很大的区别。所以我只是在问你关于它的行为的问题,仅此而已。 - Michel Feinstein
@mFeinstein:我在另一个帖子中写的内容并不是偶然或考虑不周! - László Papp
@mFeinstein:在我看来,你应该使用事件循环。我们只是在大量讨论,最终回到了同一个共同点。 - László Papp
我正在研究它,喜欢看看其他人的意见,但你通常会花费答案的一半时间来指出我做得有多糟糕,这只会把人们赶走,并使帖子里充斥着大量评论...然后你又说这是我的错。我非常感谢你的帮助Laszlo,但我们的沟通通常不太好。请保持主题,用回答回复并解释解决方案,就像@Zeta友好地那样。 - Michel Feinstein
Zeta,他贴了几乎与之前4-5个问答主题相同的代码。他仍然一遍又一遍地问几乎相同的问题。我要离开了,很抱歉。祝你们在这里帮助他好运,但我的建议是珍惜你们的空闲时间。 :-) - László Papp
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接