Qt Multithread communications [duplicate]

2019-07-07 03:36发布

This question already has an answer here:

I am new to C++ and Qt and I am having some trouble in achieving a good and not overcomplicated multithread communication environment.

Basically I have 3 threads, one for the GUI, another for handling updates sent by a device connected to the PC by USB, and another that will process the information acquired by the device and control the device and the GUI to change their state. So basically 3 threads: GUI, device and control.

My fist approach was to have device populate its private members with the information sent by USB and have some get() methods that convert this data and return it (using mutexes to assure the data was still valid). The problem is when control call the get() methods in device, it doesnt return anything new (I was expecting the methods to even never return since the thread is locked in another method, but they do return and with no new information, and also no breakpoints are triggered inside those get() methods).

The usual way Qt does inter thread communications is by using Signal and Slots, but the problem with Signal and Slots is that when one thread is processing and it has a Slot, this Slot will never be executed if some Signal is sent. Even if I could manage to use Signal and Slots to trigger new data updates I am afraid there will be lots of Signals being sent since the device updates very fast, also I have lots of data types and using QAtomicInt will not be useful for many of them so my general question is which is the best way to make the threads share data and still keep running an infinite process loop?

A good example of my goal is having something like:

Control Thread:

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

Device Thread:

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

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

I am not placing in this example the mutexes and etc, just a basic functionality to be expected.

As requested here is how I start my threads:

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);
    }
}

2条回答
ら.Afraid
2楼-- · 2019-07-07 04:12

Lets say your device works like this:

while(keep_running){
    handle_incoming_data();
}

In this scenario there's no time for your to handle anything else in this thread, since this loop doesn't exit at all. However, you can change your loop to the following construct:

public slots:
    void single_step(){
        if(keep_running){
            handle_incoming_data();
            QTimer::singleShot(0, this, SLOT(single_step()));
        }
    }

Or

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

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

Where my_timer is a QTimer* and connected to the correct slot (in this case handle_incoming_data(). Note that the 0 msec timeout has a special meaning:

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();

For more information, see the section about QTimer.

查看更多
放我归山
3楼-- · 2019-07-07 04:17

There are more than one approach for dealing with this issue:

1) Manual sleep and waking up at certain periods yourself to check if something is changed. This would be called polling by the jargon.

2) Use an event loop for your thread to process the events like signals and slots.

I would suggest the latter because the former may have the defect of literally not being able to do anything while sleeping and you may get it wrong. More importantly, you would also lose the great signal and slot mechanism all of a sudden, and your code would become somewhat more coupled than necessary.

As for doing the latter correctly, you would need to make sure to have the proper Qt event loop execution in place which is guaranteed by the later versions of QThread. That being said, please read the following documentation as I think it is useful to be aware of:

int QThread::exec() [protected]

Enters the event loop and waits until exit() is called, returning the value that was passed to exit(). The value returned is 0 if exit() is called via quit().

This function is meant to be called from within run(). It is necessary to call this function to start event handling.

You could also accomplish an in-between third solution as well, eventually, by mixing the two aforementioned approaches, namely: call the method below to explicitly make sure all the events are getting processed. It would be a semi-(a)sync way (depending on how you look at it) since you would need to do that when waking up from the sleeping for polling, but rather than dealing with the polling manually, you could use this convenience method.

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

Processes all pending events for the calling thread according to the specified flags until there are no more events to process.

You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).

In the event that you are running a local loop which calls this function continuously, without an event loop, the DeferredDelete events will not be processed. This can affect the behaviour of widgets, e.g. QToolTip, that rely on DeferredDelete events to function properly. An alternative would be to call sendPostedEvents() from within that local loop.

Calling this function processes events only for the calling thread.

查看更多
登录 后发表回答