Multi-threading in Qt Bradley T Hughes
Introduction
Bradley T Hughes
Joined Trolltech in May, 2000 Senior Software Engineer Team Lead for the Qt Platform Team Open Source Programmer
Agenda
Introduction Thread Support Overview Classes Concepts New in 4.3 and 4.4
Thread Support in Qt
What can I do with Qt?
Platform independent multi-threading Start threads Synchronize threads Store per-thread data
Thread Support in Qt
Qt provides much more...
Thread-affinity in QObject Per-thread event loops Post events to any thread Signals and slots across threads Thread-safe reference counting
Agenda
Introduction Thread Support Overview Classes Concepts New in 4.3 and 4.4
Classes
QThread
Abstract class Must reimplement 'void run()' void start() bool wait(ulong timeout) void terminate() - DO NOT USE bool isFinished() bool isRunning()
Classes
QThread
QObject subclass SIGNAL(started()) SIGNAL(finished()) SIGNAL(terminated()) Per-thread event loop int exec() SLOT(quit())
Examples class MyThread : public QThread { Q_OBJECT public: void run() { // do some work } };
9
Examples void MyClass::startThread() { myThread = new MyThread; connect(myThread, SIGNAL(finished()), SLOT(threadFinished())); thread->start(); }
10
Examples void MyClass::threadFinished() { // myThread is finished ... // cleanup delete myThread; myThread = 0; }
11
Classes
QMutex
Mutual-exclusion primitive Recursive and non-recursive (default) modes void lock(), bool tryLock() void unlock()
12
Examples void MyQueue::queueNew() { // protect data, not code mutex.lock(); Entity *entity = cache.isEmpty() ? new Entity : cache.takeFirst(); queue.append(entity); mutex.unlock(); }
13
Classes
QMutexLocker
Uses RAII Automatically lock/unlock a QMutex void unlock(), void relock() Starting with Qt 4.2, keeps state Destructor does not unlock if you call unlock()
14
Examples void MyQueue::queueNew() { QMutexLocker locker(&mutex); Entity *entity = cache.isEmpty() ? new Entity : cache.takeFirst(); queue.append(entity); }
15
Examples void MyClass::complexFunction() { QMutexLocker locker(&mutex); if (...) return; // unlocked try { ... } catch { ... throw; // unlocked } } // unlocked
16
Classes
QWaitCondition
Condition variable Thread waits until told to continue Used together with non-recursive QMutexes bool wait(QMutex *, ulong timeout) void wakeOne(), void wakeAll()
17
Examples Entity *MyQueue::dequeue() { QMutexLocker locker(&mutex); while (queue.isEmpty()) waitCondition.wait(&mutex); return queue.takeFirst(); }
18
Examples void MyQueue::queueNew() { QMutexLocker locker(&mutex); Entity *entity = cache.isEmpty() ? new Entity : cache.takeFirst(); queue.append(entity); waitCondition.wakeOne(); }
19
Classes
QSemaphore
Simple counting semaphore Constructor initializes available resources void acquire(int), bool tryAcquire(int) void release(int) int available()
20
Examples void Producer::run() { for(int i = 0; i < DataSize; ++i) { freeBytes.acquire(); buffer[i] = qrand()%4; usedBytes.release(); } }
21
Examples void Consumer::run() { for(int i = 0; i < DataSize; ++i) { usedBytes.acquire(); file.write(buffer[i]); freeBytes.release(); } }
22
Classes
QReadWriteLock
Shared-exclusive lock Multiple readers, single writer void lockForRead(), bool tryLockForRead() void lockForWrite(), bool tryLockForWrite() void unlock()
23
Examples bool MyQueue::find(Entity *e) { bool found = false; readWriteLock.lockForRead(); foreach (Entity *q, queue){ if (found = (q == e)) break; } readWriteLock.unlock(); return found; }
24
Examples void MyQueue::queueNew() { readWriteLock.lockForWrite(); Entity *entity = cache.isEmpty() ? new Entity : cache.takeFirst(); queue.append(entity); readWriteLock.unlock(); }
25
Classes
QReadLocker and QWriteLocker
RAII again This time on QReadWriteLock void unlock(), void relock() Starting with 4.2, also keeps state
26
Examples bool MyQueue::find(Entity *e) { QReadLocker locker(&lock); foreach (Entity *q, queue){ if (q == e) return true; } return false; }
27
Examples void MyQueue::queueNew() { QWriteLocker locker(&lock); Entity *entity = cache.isEmpty() ? new Entity : cache.takeFirst(); queue.append(entity); }
28
Classes
QThreadStorage
Typesafe per-thread data storage Data deleted when thread exits bool hasLocalData() T localData(), void setLocalData(T) Stores points to data
Store by value? Partial template specialization not supported in MSVC 6.0, .NET 2002
29
Examples QThreadStorage<Queue *> cache; void MyQueue::done(Entity *e) { if (!cache.hasLocalData()) { Queue *q = new Queue; cache.setLocalData(q); } cache.localData()->append(e); }
30
Examples void MyQueue::queueNew() { Queue *c = cache.localData(); Entity *entity = (!c || c->isEmpty()) ? new Entity : c->takeFirst(); QMutexLocker locker(&mutex); queue.append(entity); }
31
Agenda
Introduction Thread Support Overview Classes Concepts New in 4.3 and 4.4
32
Concepts
Thread affinity in QObject
Object belongs to a thread That thread delivers events to object Default is the thread that created the object Change with QObject::moveToThread() Per-thread event loops
33
Pop Quiz
What is the thread affinity of QApplication?
34
Pop Quiz int main(int argc, char **argv) { QApplication app(argc, argv); qDebug() << app.thread(); }
35
Pop Quiz
What is the thread affinity of QApplication?
Answer: The main() thread. Qt creates a QThread object that represents the main() thread, so app.thread() returns a valid pointer.
36
Pop Quiz
What's the thread affinity of a QTimer created in a QThread::run() reimplementation?
37
Pop Quiz void MyThread::run() { QTimer timer; qDebug() << timer.thread(); }
38
Pop Quiz
What's the thread affinity of a QTimer created in a QThread::run() reimplementation?
Answer: The MyThread instance. Since run() is called by the new thread context, timer.thread() is the same as the 'this' pointer.
39
Pop Quiz
What is the affinity of a QThread instance?
40
Pop Quiz int main(int argc, char **argv) { QApplication app(argc, argv); QThread thread; qDebug() << thread.thread(); }
41
Pop Quiz
What is the affinity of a QThread instance?
Answer: The main() thread. The QThread instance has affinity to thread that created it, not to itself. In other words, the thread is not responsible for delivering its own events. More on this later...
42
Concepts
Default thread affinity is the current thread Objects associated with thread that created them
Code examples from quiz show this clearly
Of course, thread affinity can be changed...
43
Examples void MyObject::downloadFile() { DlThread *thr = new DlThread; Downloader *dl = new Downloader; // move 'dl' to another thread dl->moveToThread(thr); ...
44
Examples connect(thr,SIGNAL(started()), dl,SLOT(loadDown())); connect(dl,SIGNAL(finished()), thr,SLOT(quit())); connect(thr,SIGNAL(finished()), this,SLOT(loadedDown())); // start downloading thread->start(); }
45
Examples class DlThread : public QThread { Q_OBJECT public: void run() { exec(); } };
46
Concepts
Posted events
QApplication::postEvent() Schedules an event for delivery Different from sendEvent() Threads handle events for their objects
47
Examples void postAnEventTo(QObject *object) { QEvent *event = new QEvent(QEvent::User); QApplication::postEvent(object, event); // event not delivered yet! }
48
Examples bool sendAnEventTo(QObject *object) { QEvent event(QEvent::User); QApplication::sendEvent(object, &event); // event has been delivered return event.isAccepted(); }
49
Concepts
Signals and slots
Communication between objects Qt::ConnectionType Direct: Slots called in context of emitter Queued: Posts an event to receiver Auto: Chooses Direct or Queued based on: Current thread Sender's thread Receiver's thread
50
Examples void MyObject::createSomeObject() { SomeObject *receiver = new SomeObject; connect(this, SIGNAL(signal())), receiver, SLOT(slot())), Qt::QueuedConnection); } void MyObject::emitSignal() { emit signal(); }
51
Examples // Tip: call a slot by name QMetaObject::invokeMethod( object, slot); // Tip: pass an argument QMetaObject::invokeMethod( object, setText, QString(Hello World));
52
Examples // Tip: but not right now QMetaObject::invokeMethod( object, setText, Qt::QueuedConnection, QString(Hello World));
53
Concepts
Implicit sharing
Copy-on-write
All shared classes are implicit All use atomic reference counting
54
Examples void MyThread::run() { QString string; forever { // implicit sharing in action string = readLargeFile(); // just pass a copy emit dataReady(string); } }
55
Examples // atomic ref count != thread-safe! QString string; void MyThread::run() { forever { // WRONG, cannot copy/modify // at the same time string = format(string); } } 56
Examples QMutex mutex; QString string; void MyThread::run() { forever { // RIGHT, protect shared data QMutexLocker locker(&mutex); string = format(string); } } 57
Agenda
Introduction Thread Support Overview Classes Concepts New in 4.3 and 4.4
58
New in 4.3
Qt::BlockingQueuedConnection
New connection type Mostly the same as QueuedConnection But current thread blocks until slot called Should only be used for receivers in a different thread
59
Examples connect( sender, SIGNAL(signal()), receiver, SLOT(slot()), Qt::BlockingQueuedConnection); QMetaObject::invokeMethod( object, methodName, Qt::BlockingQueuedConnection);
60
New in 4.3
Posted event priorities
Numerical priority can be given to any posted event Higher values have higher priority 4 is higher than zero, zero is higher than -2 Convenience enum, Qt::EventPriority Qt::HighEventPriorty = 1 Qt::NormalEventPriority = 0 (default) Qt::LowEventPriority = -1
61
Examples // posted with default priority (0) QApplication::postEvent(r, e1); QApplication::postEvent(r, e2); // higher priority QApplication::postEvent(r, e3, 1); // lower priority QApplication::postEvent(r, e4, -1); // delivery order: e3, e1, e2, e4
62
New in 4.3
Timed try-locking
Blocks current thread for a maximum timeout Returns true if lock acquired, false otherwise QMutex bool tryLock(int timeout) QReadWriteLock bool tryLockForRead(int timeout) bool tryLockForWrite(int timeout) QSemaphore bool tryAcquire(int n, int timeout)
63
Examples // wait for at most 1 second if (mutex.tryLock(1000)) { // got it ... } else { ... }
64
New in 4.3
Recursive write-locking in QReadWriteLock
Thread can lockForWrite() multiple times
QThread::idealThreadCount()
Returns number of CPU cores
QThreadStorage
Refactored to allow an arbitrary number of instances Previously hard-coded to 256
65
New in 4.4 (tentative)
QThread no longer abstract
run() is not pure virtual anymore Default implementation calls exec() Use QThread without subclassing Avoid thread affinity of QThread confusion
66
New in 4.4 (tentative)
Public atomic API
QAtomicInt Atomic operations on integers bool ref(), bool deref() Test-and-set, fetch-and-store, fetch-and-add QAtomicPointer Type-safe atomic pointer operations Test-and-set, fetch-and-store, fetch-and-add Explicit memory ordering semantics Relaxed, acquire, release, ordered Feature test API
67
New in 4.4 (tentative)
Qt Concurrent
Not discussed here Stay put, scheduled immediately after this presentation
68
Agenda
Introduction Thread Support Overview Classes Concepts New in 4.3 and 4.4
69
Thanks!
Any questions?
70