How to run a function in another thread

99 views Asked by At

I'm making an emulator and I need to run the loop that executes instructions in another thread (preferably with QtConcurrent). I am currently doing it like this

#include <QtConcurrent/QtConcurrentRun>
#include <QThread>
QFutureWatcher<void> futureWatcher;
bool running = false;

void MainWindow::startExecution() {
    running = true;
    futureWatcher.setFuture(QtConcurrent::run([this]() {
        while(running){
            qDebug() << "Physical Thread ID: " << QThread::currentThreadId();
            QThread::msleep(100);
            //execute instructions
        }
    }));
}

void MainWindow::stopExecution()
{
    running = false;
    futureWatcher.waitForFinished();
}

The issue is every time that futureWatcher is run, it starts running in another thread. And after 8 runs (or how many threads my CPU has) the futureWatcher won't run anymore. I proved this by doing this:

qDebug() << "Physical Thread ID: " << QThread::currentThreadId();

This is called as soon as the futureWatcher starts.

Snippet of the output:

called from Physical Thread ID:  0x2768
Physical Thread ID:  0x8c4
called from Physical Thread ID:  0x2768
Physical Thread ID:  0x1414
called from Physical Thread ID:  0x2768
Physical Thread ID:  0x20d0
called from Physical Thread ID:  0x2768
Physical Thread ID:  0x2768
called from Physical Thread ID:  0x2768
Physical Thread ID:  0x2768
called from Physical Thread ID:  0x2768
Physical Thread ID:  0x2768

As soon as the threads are the same, it no longer behaves correctly and the loop doesn't run when setFuture is called, but it is actually run when futureWatcher.waitForFinished(); is called in stopExecution().

I only need 2 threads. One is the main thread and the other for the execution. Is there a way to always run the future watcher in the same second thread? What other solutions can I use?

2

There are 2 answers

0
grand titan On

I found an error. In the function that actually executes instructions there is one specific condition where that function calls stopExecution. I believe because that is called from the same thread that the futureWatcher is running, when futureWatcher.waitForFinished(); is called, the next time futureWatcher.setFuture is called it is started on another thread. This assumption is based on testing the code with and without the condition that calls stopExecution. I solved the issue by replacing every stopExecution call from inside the futureWatcher with running = false;. Now the code works as intended. The previous solution with thread pools is unnecessary.

1
Esat Akkaşoğlu On

The problem you are encountering is that QtConcurrent::run by default uses the global QThreadPool, which has a limited number of threads (usually as many as the number of CPU cores). Once all threads are in use, additional calls to QtConcurrent::run will queue the tasks until a thread becomes available.

In your case, because running is not turned false before calling waitForFinished(), the threads may not be properly released back to the pool. This is why after several runs, you no longer have any threads available to run new tasks.

If you want to ensure that you always have a thread available for your emulator's execution loop, you could create your own QThreadPool with a single thread and use that for your concurrent tasks.

Here's a rough example of how you could modify your code:

#include <QThreadPool>
#include <QtConcurrentRun>

QThreadPool executionPool;
QFutureWatcher<void> futureWatcher;
bool running = false;

void MainWindow::startExecution() {
    running = true;
    executionPool.setMaxThreadCount(1); // Ensure only one thread is in this pool

    // Pass the custom thread pool to QtConcurrent::run
    futureWatcher.setFuture(QtConcurrent::run(&executionPool, [this]() {
        while(running){
            qDebug() << "Physical Thread ID: " << QThread::currentThreadId();
            QThread::msleep(100);
            //execute instructions
        }
    }));
}

void MainWindow::stopExecution() {
    running = false;
    futureWatcher.waitForFinished(); // This will now properly wait for the task to finish
}

// In your destructor, make sure to shutdown the thread pool
MainWindow::~MainWindow() {
    executionPool.waitForDone(); // Wait for all tasks to finish
}

With this setup, you're controlling the thread pool directly and ensuring that only one thread will be used for your task, separate from the global thread pool. This should prevent the exhaustion of threads in the global pool.

Remember to handle thread safety when accessing and modifying shared resources between the main thread and the execution thread. If running is shared, it should at least be an atomic variable to prevent race conditions.

Finally, make sure to properly clean up the thread pool when your application is closing to prevent any threads from continuing to run in the background.

Resource: ChatGPT-4