0

Take the example from the Qt docs: one creates a controller and a worker, moves the worker to the specific thread and starts the thread. Start/stop triggering are done via signal/slot.

Now assume that the doWork method depends on some parameter (for example an output rate of intermediate results (in form of some visualization)) and one would like to tweak that parameter via a user interface while the function is running. If one uses the signal/slot mechanism (between interface and worker) this won't work, as the receiving slot of the worker will be queued after the running event loop and thus will be executed after doWork finished.

I came up with the following solutions:

Method 1:

Make the parameter public in Worker and change it directly via the interface.

class Worker : public QObject
{
    Q_Object

public:
    int parameter;
    ...
}

Interface::updateParameter(int p) { worker_instance.parameter = p; }

Method 2:

Move the parameter to either the interface itself or some extra class (living in another thread than the worker) and make the worker load it each time it's required.

class Interface : public QWidget
{
    Q_Object

private:
    int parameter;

public:
    int getParameter() { return parameter; }
    ...
}

Worker::doWork() {
    ...
    interface_instance.getParameter();  // load parameter;
    ...
}

Instead of placing the parameter within the interface one could place it in another class and interact with this class via signal/slot mechanism.

Regarding ease of use I'd definitely prefer Method 1. It also makes things stay where they actually belong to. Method 2 doesn't really seem to offer an advantage over Method 1. Any opinions on that?

Also are there any different approaches to handle things like that? For example viewing an animation, I guess one wants to enable adjustemt of the animation speed which somehow requires a hook into the animation process.

Do you have any ideas or knowledge on this topic?

dtech
  • 47,916
  • 17
  • 112
  • 190
a_guest
  • 34,165
  • 12
  • 64
  • 118

3 Answers3

1

If one uses the signal/slot mechanism (between interface and worker) this won't work, as the receiving slot of the worker will be queued after the running event loop and thus will be executed after doWork finished.

This is not the necessarily case, you can make doWork() non-blocking, you do a portion of the work, then let the event loop cycle, allowing communication with other threads, you can use queued connections to transfer data this way, then do another work step, another cycle and so on. This allows you to monitor progress, pause, cancel work and transmit data back and forth during.

Blocking vs non-blocking worker:

main            main    
|       worker  |       worker
|------>|       |------>|
|       |       |       |
|       |       |<----->
|       |       |       |
|       |       |       |
|<------|       |<----->
|               |       |
|               |<------|
|               |   

Here I have set an example of breaking up work into steps and running the steps without blocking the worker thread to allow communications to get through. The concept is the same - split work into work units, track the state in the worker object, use signals and slots with parameters for control and data transfer.

Just take care not to go overboard with queued connections, because they are very slow compared to direct connections, if you flood the event loop you will actually lose performance and the application is likely to hang. Considering the goal is usually visual or user control feedback, try not to issue too many queued connections per second. Something like 30 per second is a sweet spot for responsive UI, but you could even do with less.

Community
  • 1
  • 1
dtech
  • 47,916
  • 17
  • 112
  • 190
  • Ok, so if I understand you correctly, you mean to split the work into smaller work units and make those units (public) slots, so they actually get queued and can be "interrupted" by the parameter control signals? – a_guest Jan 19 '16 at 00:21
  • @a_guest - there are numerous ways to achieve this. In the example I linked the worker self-schedules if not interrupted, buy you could do the scheduling manually as well. There is no golden rule, whatever works best for you. The main thing is that you don't block the worker thread so it can communicate with the main thread. – dtech Jan 19 '16 at 02:38
1

It sounds a bit like you want thread synchronisation here, i.e. mutex and thread lock/unlock.

So in your code you would declare a mutex variable (if you have many workers and your parameter is worker specific then it should live publicly in the worker class, otherwise if its a "singleton" parameter then stick it in main). This is effectively a global variable. You also need a global variable that is your parameter.

Then in your doWork() function:

Worker::doWork() {
    ...
    // Get the parameter value
    g_mutex.lock();
    // take copy of the global parameter (m_ is member and g_ is global)
    m_param = g_param;  
    g_mutex.unlock();
    ...
}

Then wherever you need to acutally set/change the parameter:

    // Set the parameter value
    g_mutex.lock();
    g_param = ...;  
    g_mutex.unlock();

This ensures that the parameter is not overwritten / modified as you are trying to read it (if you are bothered). It's good thread-safe practise to do this. But its more important on the "set" side to use the mutex.

You can get away with no mutex on reads since you may not really care about someone writing to it at the same time (if its a single instruction write like a POD), but if its a structure then you probably will care.

Another way is to re-work your code so that your work units are smaller (I think like what @ddriver is getting at ... seems pretty good idea.

code_fodder
  • 15,263
  • 17
  • 90
  • 167
  • Good comment, I haven't thought about "data races" in this context, also because it doesn't really matter in my case. In your example what would `g_mutex.lock()` actually do? Is it important to be declared as a slot? When is the main sequence resumed and `m_param = g_param` assumed to be safe (i.e. no other thread writes to it)? It's not really clear how `lock()` can wait until `unlock()` was called from another thread without introducing another "data race" within `g_mutex`. Also is it really safe to write to the same variable (that lives within a specific thread) from different threads? – a_guest Jan 19 '16 at 00:31
  • So the mutex is a QMutex (otherwise you would use pthread_mutex_lock() for std c++). When you call mutex.lock() the calling thread blocks until that mutex is locked. It means you have a first come first served idea so there is not a "race" as such. `m_param = g_param` is always safe because it will have locked the mutex and so no-one else will be trying to read or write to it while this assignment takes place. The last part of your question: yes, but note this is (in my example) a global variable so it is accessable to all. If it were me I would encapsulate the parameters into a new data class – code_fodder Jan 19 '16 at 07:15
  • (ran outta space) the data class would be a singleton global class with a bunch of set/get functions to store and access your data. It would have all the mutex's within so that from anywhere in your code you can safely call setParamXyz() and getParamXyz(). There is a design pattern for this, but I can't recall the name... i'll look it up. – code_fodder Jan 19 '16 at 07:18
  • One other useful tool in the "thread sync toolbox" is the conditional variable, where you can get your thread to sit and wait for a specific "condition" before it carries on... but looking at your code, I don't think that is what you want. But FYI there is a simple example for Qt here: http://doc.qt.io/qt-4.8/qwaitcondition.html (look at destailed description) – code_fodder Jan 19 '16 at 07:25
0

This depends on the nature of your doWork. Is it expected to consume 100% of CPU, or is it event-based, triggered by something like a timed event? You mentioned visualisation, which means it is more like a timed event, where you wake up your thread, process/update the data, and then sleep until it is time to render the next frame.

If this is the case, you can indeed use the signal-slot to update data. To do this, you do not implement run(), thus using the default implementation (which uses its own event loop). In start() function then you queue a QTimer event to invoke your doWork(), and call the parent start(). The event loop starts, invokes your doWork(), you process your data (at your own pace - this is not GUI thread, you're not blocking anything), and at the end of doWork you queue another QTimer event again, and return to the event loop.

Since it is running the event loop, all signal/slot processing will work in this case. Even more, the queued connection signal/slot would be synchronous by definition, meaning you do not need to synchronize the data access.

George Y.
  • 11,307
  • 3
  • 24
  • 25
  • All workers are event driven, regardless of whether they are blocking or non-blocking. Also, the worker can be non-blocking even if it is not based on a continuous timed event. Workers don't need overridden thread, since they are event driven they can work with the default thread implementations, which starts its event loop which is used to control the worker. – dtech Jan 15 '16 at 13:45
  • Then you should be ok with subclassing QThread and *not* implementing run() - which will have the thread running its own loop. Then you can call its slots via queued connection from your events, and have them processed in a separate thread, so the processing wouldn't block your main thread. – George Y. Feb 16 '16 at 23:04