In ICS’ latest technical webinar Justin Noel will teach you how to leverage Qt's cross thread communications systems to marshall data to and from communications threads using QThreads (or threads owned by third party libraries.) He’ll show you that by using the lockless producer consumer pattern you can write efficient and easy to read code without needing to add mutexes to your data structures, important because data read by QML bindings needs to be as quick as possible to avoid dropping frames. Don’t miss it!
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Lockless Producer Consumer Threads: Asynchronous Communications Made Easy
1. Integrated Computer Solutions Inc. www.ics.com
Lock Less With Producer
Consumer Threads
Efficiently Marshalling Data from Producer Threads
1
2. Integrated Computer Solutions Inc. www.ics.com
Common Scenarios
Data communications may be a blocking operation
Need to spawn a thread to handle it.
Data communications happens in another thread
This thread may not even be created within your application.
3rd party library spawns this thread
Callbacks or Listeners are called from this thread and NOT the main GUI thread.
Data structures need to be protected.
One thread isn’t reading while another thread is writing.
2
3. Integrated Computer Solutions Inc. www.ics.com
QML Performance Considerations
QML bindings require properties to return as quick as possible
Properties may be used in N different bindings
Bindings may cause properties to be fetched quite often
value : cppModel.a + cppModel.b + cppModel.c
3
4. Integrated Computer Solutions Inc. www.ics.com
Lockless Thread Communications
Not to be confused with “Lock Free” thread communications
Where there are no locks.
We are going to use fewer locks and lock them less often.
Lockless is much like stainless steel.
It can rust, but a whole lot less than steel.
Less can be more when it comes to locking threads.
4
5. Integrated Computer Solutions Inc. www.ics.com
Less Efficient Solution (Model Locks)
Locks inside data models
Every setter locks a mutex (or other lock)
Every getter locks a mutex (or other lock)
This is fairly low risk. All locks are hidden from client code.
Client code usually can’t cause deadlock
However, there is a whole lot of locking going on
A value maybe updated often (writing)
That value could be used in a lot of QML bindings (reading)
5
6. Integrated Computer Solutions Inc. www.ics.com
Error Prone Solution (External locking)
Client code locks and unlocks model.
May be better because you can lock once, update as much as you like, unlock.
Less thread context switching.
Error prone because client code can now cause a deadlock bug
Because of the way QML works you can’t bundle reads.
Would still lock on every property read.
Or you’d have to write some hairy code to copy the data.
6
7. Integrated Computer Solutions Inc. www.ics.com
Producer Consumer Solution
Updated values are added to a shared queue
Communications thread adds to the queue or ring buffer
GUI thread takes items off the queue or ring buffer
GUI thread would need a way to monitor the queue for new values
Event loop integration, etc
There is a fair bit of non-trivial code to write for this solution
If you used some other toolkit…..
Much less locking involved.
7
8. Integrated Computer Solutions Inc. www.ics.com
Qt’s Automagical Producer Consumer System
Qt has a shared queue to communicate with the GUI thread
The Qt event loop!
Qt has a simple way to integrate with the event loop
Cross thread signals and slots.
We can simply use this mechanism to get producer consumer with very little code
8
10. Integrated Computer Solutions Inc. www.ics.com
How Cross Thread Signals and Slots Work
Qt signals and slots have 4 connection types
Direct - Signal causes connected slots to be called via blocking function calls
This is how most slots are called. You can step from signal to slot.
Queued - Signal causes events to be added to an event queue
These events copy the parameters.
The event calls the slot when handled.
BlockingQueuedConnection - Queued, but blocks the calling thread.
Avoid using this. If you use it wrong you will deadlock.
10
11. Integrated Computer Solutions Inc. www.ics.com
AutoConnection
AutoConnection - Direct vs Queued is decided when a signal is emitted.
The thread id of the current thread (where emit happens) is checked
against the receiver object’s “preferred thread”.
The Qt docs call this the QObject’s thread affinity.
Thread ids match - Direct Connection
Thread ids do match - Queued Connection
11
12. Integrated Computer Solutions Inc. www.ics.com
How To Set QObject Thread Affinity
QObjects default to the thread they were created in.
QObject::moveToThread(QThread* thread)
This will cause slots to be called in thread’s context when using auto
connection
Receivers always need to prefer the main thread or a QThread.
Because being on the receiving end of a cross thread signal requires an
event loop
For our purposes we don’t need to move any objects.
All the receivers prefer the main gui thread.
12
13. Integrated Computer Solutions Inc. www.ics.com
Emitting From Other Threads
No special code required.
You can emit from any thread.
Does not need to be a QThread
Could be pthread, std::thread, etc
13
14. Integrated Computer Solutions Inc. www.ics.com
Registering QVariant Types
Parameters passed via cross thread signals and slots need to be QVariant
compatible
Default constructor
Copy constructor
Assignment operator
Use Q_DECALRE_METATYPE(TypeName) in header
Use qRegisterMetaType<TypeName>(“TypeName”) in code
Enums also have to registered
14
15. Integrated Computer Solutions Inc. www.ics.com
What about costly copies? Implicit sharing!
Most Qt data structures are implicitly shared.
QString, QByteArray, QImage
QVector, QList, QMap, QHash, etc
Copy on write semantics
Assignment operator only increments a reference count
Makes passing large data between threads much faster.
As long as you don’t change the data. That will cause a detach().
15
16. Integrated Computer Solutions Inc. www.ics.com
Implicit Sharing
// 10 MB Char Array
QByteArray hugeData(1024 * 1024 * 10, ‘X’);
// Reference count increment.
// No memory allocated here for anotherArray.
// Points to hugeArray’s data.
QByteArray anotherData = hugeData;
// Modify the anotherData array.
// This will call detach() which decrements the ref count and copies hugeData
// before modifying one char in the array.
anotherData[4242] = ‘Y’;
16
17. Integrated Computer Solutions Inc. www.ics.com
Implicit Sharing Tips
Implicit sharing is a blessing and a curse
Good - “Copies” of unmodified data are very fast.
Good - Reference count is thread safe using atomics
Bad - Debugging performance issues can become a hassle
It’s not obvious where data is copied.
Look for stacks with ::detach() in profiling data
17
20. Integrated Computer Solutions Inc. www.ics.com
Pros and Cons
Pros:
Faster reads for QML properties.
Much simpler code. Easier to maintain.
Only need to understand basic Qt Signals and Slots to extend.
Threading details are completely hidden.
Cons:
Very high frequency data can log jamb the event queue.
20