PDA

View Full Version : Share data accross threads and emit signals



Lykurg
17th March 2015, 09:33
Hi,

I need to have access to several information from different threads so I made a class like this:

class Foo
{
public:
void increase()
{
QWriteLocker l(&m_lock);
m_ints << 42;
}

private:
QReadWriteLock m_lock;
QVector<int> m_ints;
};
Foo is then instantiated in the main thread and a pointer is passed to the relevant threads. So far so good. Now I'd like to get informed if e.g. m_ints gets changed. So QObject came into play but as it is only reentrant (except connect and disconnect) it is getting complicated.

Let's think of this modified class:

class Foo : public QObject
{
public:
Foo: QObject(0) {}

void increase()
{
QWriteLocker l(&m_lock);
m_ints << 42;
emit added(42);
}

signals:
void added(int);

private:
QReadWriteLock m_lock;
QVector<int> m_ints;
};
So, Foo's thread affinity is my main thread.

Is this class still thread-safe since it does not use any functions of QObject? What about emit?
What does this mean for calling increase() through a pointer from a different tread. Which thread does the "work" (manipulating m_ints) of increase(): The main thread or the local thread?


If that solution is bad, what would be a good approach to get informed about changes? (Beside the caller of the function emits afterwards the changes ala

myFoo->increase();
emit mySignalAdded(42);


Thanks
Lykurg

anda_skoa
17th March 2015, 09:50
Is this class still thread-safe since it does not use any functions of QObject? What about emit?

That should be ok.



What does this mean for calling increase() through a pointer from a different tread. Which thread does the "work" (manipulating m_ints) of increase(): The main thread or the local thread?

The thread calling the method is the one executing its code.
If the receiver of the signal has a different thread affinity than the thread executing the emit, then the slot will be executed by the receiver's owner thread (assuming Qt::AutoConnection).

However, I would suggest to unlock before emitting, otherwise it is easy to get into a recursive lock situation (e.g. the slot or a methods called from the slot calling a method of this object again).

Cheers,
_

wysota
17th March 2015, 10:33
An alternative approach would be to use has-a instead of is-a approach, similar to what QFutureWatcher does. You can register an external QObject with your class, keeping your class thread-safe with the use of the lock and make the object inform the watcher (through e.g. QCoreApplication::postEvent()) that its content has changed (or whatever else you need). You may also look into lock-free structures if your vector only grows at the end to completely avoid the need for a lock.

Lykurg
17th March 2015, 10:43
The thread calling the method is the one executing its code.
If the receiver of the signal has a different thread affinity than the thread executing the emit, then the slot will be executed by the receiver's owner thread (assuming Qt::AutoConnection).
Not sure if I get you right (second sentence): Suppose I have two threads A and B beside my main thread MT.

// in MT
Foo *f = new Foo;
objInThreadA->setFoo(f);
objInThreadB->setFoo(f);
connect(foo, &Foo::added, objInThreadB, &Bar::doSomething); // autoConnection will use queuedConnection

// in Thread A
f->increase();

So who does what? My understanding is: ThreadA does the work in increase() and then ThreadB do with "doSomething" whatever it likes in his thread.



However, I would suggest to unlock before emitting, otherwise it is easy to get into a recursive lock situation (e.g. the slot or a methods called from the slot calling a method of this object again).

Yes, recursive lock is a risk but is emit thread safe? Doesn't it involve QMetaObject etc. With lifting the lock before emitting, wouldn't that open the possibility that two threads run in the same emit simultaneously?

Added after 4 minutes:


An alternative approach would be to use has-a instead of is-a approach, similar to what QFutureWatcher does. You can register an external QObject with your class, keeping your class thread-safe with the use of the lock and make the object inform the watcher (through e.g. QCoreApplication::postEvent()) that its content has changed (or whatever else you need).
Okay, have to think about that. Thanks for the tip.

You may also look into lock-free structures if your vector only grows at the end to completely avoid the need for a lock.
Sure, that's the second step but the deadline leaves me no time to do that (at least for now)...

P.s.: Thanks to you, too, anda_skoa! Forget that in my previous reply.

anda_skoa
17th March 2015, 11:21
Not sure if I get you right (second sentence): Suppose I have two threads A and B beside my main thread MT.

// in MT
Foo *f = new Foo;
objInThreadA->setFoo(f);
objInThreadB->setFoo(f);
connect(foo, &Foo::added, objInThreadB, &Bar::doSomething); // autoConnection will use queuedConnection

// in Thread A
f->increase();

So who does what? My understanding is: ThreadA does the work in increase() and then ThreadB do with "doSomething" whatever it likes in his thread.

Yes, correct.



Yes, recursive lock is a risk but is emit thread safe? Doesn't it involve QMetaObject etc. With lifting the lock before emitting, wouldn't that open the possibility that two threads run in the same emit simultaneously?

I don't think emitting a signal changes anything inside the object, so that should be OK.

Cheers,
_

Lykurg
17th March 2015, 13:35
Not that I do not trust you :p but I wanted to be sure, so in case anyone else stumbles across this thread: emit is thread safe. It calls QMetaObject::activate which then uses a mutex.

Detailed information can be found at the end of this article: http://woboq.com/blog/how-qt-signals-slots-work.html

Thanks!