PDA

View Full Version : How to send a unique copy of a signal to several separate objects?



nthexwn
12th January 2016, 02:07
Let's say I have 100 worker objects running on 100 separate threads. At some point I'd like to send a triggerSomething signal to the doSomething slot on each of these objects. This is not that difficult. When I create each object I simply connect the triggerSomething signal to the doSomething slot on each object. Then when I emit doSomething all of the worker objects' doSomething slots will be called at the same time:



// Inside some class

QPair<Worker*, QThread*> workersAndThreads;

void setupWorkers()
{
workersAndThreads = new QPair<Worker*, QThread*>[100];
for(int i = 0; i < 100; i++)
{
// Create worker
Worker* worker = new Worker();
workersAndThreads[i].first = worker;

// Create thread
QThread* thread = new QThread();
workersAndThreads[i].second = thread;

// Assign worker to the thread
worker->moveToThread(thread);

// Connect the worker
connect(this, SIGNAL(triggerSomething()), worker, SLOT(doSomething()));

// Start the thread (note that the worker isn't doing anything yet).
thread->start();
}
}

void makeAllTheWorkersDoSomethingAtTheSameTime()
{
emit triggerSomething();
}



Unfortunately, if I need all of the worker objects to do something at different points in time then this won't work. The triggerSomething signal is hooked up to all of the workers, so there's no way to emit the signal and have it trigger only one of these workers. I can think of two horrible work-arounds for this. First, we could pass an ID in the signal and have each worker object check its ID in the slot to make sure that the signal was really for it. If it was, then it would do something, and if it wasn't then it would disregard the signal. This is very wasteful though since, if there are 100 separate emits (for the 100 objects), there end up being 9,900 unnecessary calls to slots (for the objects whose IDs didn't match the signal). A somewhat better approach which I'm inclined to use right now is this:



void makeASpecificWorkerDoSomething(int indexOfSpecificWorker)
{
// disconnect this object from all previous triggerSomething connections since we don't want to call them all at once.
disconnect(this, SIGNAL(triggerSomething()), 0, 0)

// connect this object to the specific Worker object that we want to call.
Worker* specificWorker = workersAndThreads[indexOfSpecificWorker].first;
connect(this, SIGNAL(triggerSomething()), specificWorker, SLOT(doSomething())

// signal the specific worker object
emit triggerSomething();
}


This accomplishes what I want, but unfortunately it requires a disconnect and a connect every time I want to emit a signal. I'm not sure how costly the connect/disconnect operations are, but I'm inclined to believe that there's a better way to do this.

Any ideas?

Lesiok
12th January 2016, 08:11
Do not use signals only QMetaObject::invokeMethod.

yeye_olive
12th January 2016, 10:36
Lesiok's solution clearly is the way to go with your current infrastructure.

However, allocating a QThread for each worker is expensive. Do you really need the workers to run in separate (and persistent) threads, or do you only need them to run outside the main (GUI?) thread, in as many threads as the system can effectively run concurrently? In the latter case, consider using QtConcurrent::run().

nthexwn
12th January 2016, 23:03
Do not use signals only QMetaObject::invokeMethod.

So if I'm understanding this correctly, I want something like this instead?



void makeASpecificWorkerDoSomething(int indexOfSpecificWorker)
{
// Look up worker
Worker* specificWorker = workersAndThreads[indexOfSpecificWorker].first;

// Schedule worker to do something in its own event loop
QMetaObject::invokeMethod(worker, SLOT(doSomething()), Qt::QueuedConnection);
}




Do you really need the workers to run in separate (and persistent) threads, or do you only need them to run outside the main (GUI?) thread, in as many threads as the system can effectively run concurrently? In the latter case, consider using QtConcurrent::run().

I believe I do need them to run in their own persistent threads. Each thread is running a separate copy of a game. Each game uses the event loop on that thread to interface with an AI (or player) either directly (compiled into the program) or through the network. As far as I can tell QtConcurrent just launches a single function per thread and doesn't provide event loops. Is that correct?

I suppose I could re-write the game to use its own manually written loop inside a main function instead of relying on the event loop provided by QThread. Do you believe this would be a better approach?

yeye_olive
13th January 2016, 10:34
So if I'm understanding this correctly, I want something like this instead?



void makeASpecificWorkerDoSomething(int indexOfSpecificWorker)
{
// Look up worker
Worker* specificWorker = workersAndThreads[indexOfSpecificWorker].first;

// Schedule worker to do something in its own event loop
QMetaObject::invokeMethod(worker, SLOT(doSomething()), Qt::QueuedConnection);
}


Almost. As the documentation for QMetaObject::invokeMethod() specifies: You only need to pass the name of the signal or slot to this function, not the entire signature. Which yields:

QMetaObject::invokeMethod(worker, "doSomething", Qt::QueuedConnection);
Notice that you could omit the argument Qt::QueuedConnection, because doing so uses the default Qt::AutoConnection, which behaves like Qt::QueuedConnection in this case (worker does not live in the thread executing QMetaObject::invokeMethod()).


I believe I do need them to run in their own persistent threads. Each thread is running a separate copy of a game. Each game uses the event loop on that thread to interface with an AI (or player) either directly (compiled into the program) or through the network. As far as I can tell QtConcurrent just launches a single function per thread and doesn't provide event loops. Is that correct?
That is correct, QtConcurrent::run() only schedules a function to be run in another thread taken from a thread pool once one becomes available. Since you need event loops, your current solution looks reasonable. If, during the optimization phase of your project, you realize that too many threads remain idle often enough, then you could try to run several instances of the game in each thread.


I suppose I could re-write the game to use its own manually written loop inside a main function instead of relying on the event loop provided by QThread. Do you believe this would be a better approach?
Probably not, unless performance becomes an issue and you can measurably improve it by tailoring the event loop to your needs. Your current approach seems well adapted to your use case at this stage.