Qt Concurrent
Morten Johan Sørvig
About Me
Morten Sørvig
Pronounced like "more–ten sir–wig"
Software developer at Trolltech since 2004
Responsibilities:
Threading
Qt/Mac
Agenda
Introduction
API in detail
Demo
3
Note!
Qt Concurrent is still in development
Scheduled for release with Qt 4.4
API details are not finalized yet
Preview at Trolltech Labs: labs.trolltech.com
4
Introduction
5
Multi-Core Hardware
Single-core CPU performance has stagnated
Multi-Core chips are used instead
6
Multi-Core Software
Think parallel!
First step: Identify tasks that can be parallelized
7
qt3to4
AST
Parse source files in parallel
cpp
cpp AST
cpp AST
8
Assistant
Index each document in
parallel
9
Image Viewer
Load and scale images in
parallel
10
What's next?
Thinking parallel was not that hard
Implementing it is a problem
"I know, I'll use threads!"
11
I'll use threads!"
... and now you have two problems.
12
Threads Considered Harmful
Non-deterministic
Requires synchronization
Dead-locks & race conditions
How many threads should I use?
13
Threads Considered Useful After All
Conclusion: Low abstraction level
“The assembler of parallel programming.”
But it's what we've got
Let's build something better on top of threads
14
Structured Threaded Programming
Abstract away thread management
Abstract away synchronization
Event loop integration
Less code!
15
API in Detail
16
Overview
QtConcurrent namespace
run()
map(), mapped(), mappedReduced()
Synchronization objects
QFuture
QFutureWatcher
17
Two Aspects
Concurrent – Asynchronous behavior
Parallel – Scaling on multi-core
18
Asynchronous Behavior - run()
void hello()
{
qDebug() << "Hello World";
}
QtConcurrent::run(hello);
19
Behind the Scenes
run() acquires a thread from a thread pool
Thread limit: the number CPU cores on the system
Enqueued if no threads are available
20
Synchronizing
QFuture<void> greeting = run(hello);
...
greeting.waitForFinished();
21
Future?
"An object that acts as a proxy for a result that is
initially not known" – Wikipedia
Introduced in a research paper by Baker and Hewitt in
1977
Slowly finding its way into mainstream languages
22
QFuture<T>
Lightweight, reference counted
Stores one or more results
Provides synchronization using polling or blocking
23
Return values
QImage loadImage()
{
Qimage image;
image.load("foo.jpg");
return image;
}
QFuture<QImage> loading = run(loadImage);
...
QImage image = loading.result();
24
Return values
QImage loadImage()
{
Qimage image;
image.load("foo.jpg");
return image;
}
QFuture<QImage> loading = run(loadImage);
...
QImage image = loading.result();
25
Function Arguments
QImage loadImage(const QString &fileName)
{
Qimage image;
image.load(fileName);
return image;
}
run(loadImage, QLatin1String("foo.jpg"));
26
Member Functions
class Greeter
{
void sayHello()
{
qDebug() << "Hello World";
}
};
Greeter greeter;
run(&Greeter::sayHello, &greeter);
run(&Greeter::sayHello, greeter);
27
Non-blocking Synchronization
QFuture provides blocking synchronization
Blocking the GUI thread is a Bad Idea.
28
QFutureWatcher<T>
QObject
Non-blocking synchronization
Callbacks using signals and slots
29
QFutureWatcher
class Greeter : public QObject
{
Q_OBJECT
private:
void sayHello();
private slots:
void greetingCompleted();
};
30
QFutureWatcher
// In the class constructor:
QFutureWatcher<void> *greeting = new
QFutureWatcher<void>(this);
connect(greeting, SIGNAL(finished()),
SLOT(greetingCompleted()));
...
// In a member function:
greeting->setFuture(run(&Greeter::sayHello, this));
31
QFutureWatcher
// In the class constructor:
QFutureWatcher<void> *greeting = new
QFutureWatcher<void>(this);
connect(greeting, SIGNAL(finished()),
SLOT(greetingCompleted()));
...
// In a member function:
greeting->setFuture(run(&Greeter::sayHello, this));
32
QFutureWatcher
// In the class constructor:
QFutureWatcher<void> *greeting = new
QFutureWatcher<void>(this);
connect(greeting, SIGNAL(finished()),
SLOT(greetingCompleted()));
...
// In a member function:
greeting->setFuture(run(&Greeter::sayHello, this));
33
Summary – Asynchronous behavior
Use run() and function pointers to specify what the
worker threads should do
QFuture provides blocking synchronization
QFutureWatcher provides non-blocking
synchronization
34
Scaling for Multi-Core
Run a function on each item in a container in parallel
Similar to map and filter from Python
35
map
void scale(QImage &image);
...
QList<QImage> images;
map(images, scale);
36
mapped
QImage scaled(const QImage &image)
...
const QList<QImage> images;
QList<QImage> thumbnails
= mapped(images, scaled);
37
mappedReduced
QImage scaled(const Qimage &image);
void combined(QImage &final,
const QImage& intermediate);
...
const QList<QImage> images;
QImage collage =
mappedReduced(images, scaled, combined);
38
mappedReduced
QImage scaled(const Qimage &image);
void combined(QImage &final,
const QImage& intermediate);
...
const QList<QImage> images;
QImage collage =
mappedReduced(images, scaled, combined);
39
Filter – filtered - filteredReduced
bool isColorImage(const QImage &);
...
QList<QImage> colorImages =
filtered(images, isColorImage);
40
Member functions
void QImage::invertPixels();
...
QList<QImage> images;
map(images, &QImage::invertPixels);
41
Iterators
Iterator ranges can be specified instead of containers
QList<QImage> images;
map(images.begin(), image.end(),
&QImage::invertPixels);
42
Summary – scaling for multicore
Use function pointers to specify what the threads
should do
Use containers or iterators to provide the data ranges
map mapped mappedReduced
43
Recap
Concurrent
Parallel
Can they be combined?
44
Two Versions of mapped()
Synchronous:
QList<T> QtConcurrent::blocking::mapped(...)
Asynchronous:
QFuture<T> QtConcurrent::mapped(...)
45
QFutureWatcher supports:
Result notifications
Progress notifications
Canceling
46
Result Notifications
void QFutureWatcher::resultReadyAt(int index)
T QFutureWatcher::resultAt(int index)
47
Result Notifications
class ImageHandler public: QObject
{
QFutureWatcher<QImage> *watcher;
public:
ImageHandler()
{
watcher = new ...
connect(watcher, SIGNAL(resultReadyAt(int)),
SLOT(imageReadyAt(int)))
}
private slots:
void imageReady(int index)
{
QImage image = watcher->resultAt(index);
}
};
48
Result Notifications
class ImageHandler public: QObject
{
QFutureWatcher<QImage> *watcher;
public:
ImageHandler()
{
watcher = new ...
connect(watcher, SIGNAL(resultReadyAt(int)),
SLOT(imageReadyAt(int)));
}
private slots:
void imageReady(int index)
{
QImage image = watcher->resultAt(index);
}
};
49
Result Notifications
class ImageHandler public: QObject
{
QFutureWatcher<QImage> *watcher;
public:
ImageHandler()
{
watcher = new ...
connect(watcher, SIGNAL(resultReadyAt(int)),
SLOT(imageReadyAt(int)))
}
private slots:
void imageReady(int index)
{
QImage image = watcher->resultAt(index);
}
};
50
Too Many results?
Now: 2-4 worker threads, 1 GUI thread
Soon: 8-16 worker threads, 1 GUI thread?
QFutureWacher throttles the worker threads
51
Progress Notification and Canceling
Progress updates:
progressRangeChanged(minimum, maximum)
progressValueChanged(value)
Canceling:
cancel()
52
Connecting a QProgressDialog
53
Connecting a QProgressDialog
QProgressDialog *dialog = ...
QFutureWatcher<void> *fw = ...
connect(fw, SIGNAL(progressRangeChanged(int, int)),
dialog, SLOT(setRange(int, int)));
connect(fw, SIGNAL(progressValueChanged(int)),
dialog, SLOT(setValue(int)));
54
Connecting a QProgressDialog
QProgressDialog *dialog = ...
QFutureWatcher<void> *fw = ...
connect(fw, SIGNAL(progressRangeChanged(int, int)),
dialog, SLOT(setRange(int, int)));
connect(fw, SIGNAL(progressValueChanged(int)),
dialog, SLOT(setValue(int)));
55
Connecting a QProgressDialog
QProgressDialog *dialog = ...
QFutureWatcher<void> *fw = ...
connect(fw, SIGNAL(progressRangeChanged(int, int)),
dialog, SLOT(setRange(int, int)));
connect(fw, SIGNAL(progressValueChanged(int)),
dialog, SLOT(setValue(int)));
56
Connecting a QProgressDialog
connect(fw, SIGNAL(finished()),
dialog, SLOT(reset()));
connect(dialog, SIGNAL(canceled()),
fw, SLOT(cancel()));
fw->setFuture(map(list, function));
dialog->exec();
57
Connecting a QProgressDialog
connect(fw, SIGNAL(finished()),
dialog, SLOT(reset()));
connect(dialog, SIGNAL(canceled()),
fw, SLOT(cancel()));
fw->setFuture(map(list, function));
dialog->exec();
58
Demo
Load images
Scale down
Display thumbnails
QThread
QtConcurrent
59
End
Questions?
60
Emergency Slides
61
Jambi support
Planned for the Qt 4.4 release.
Java lacks function pointers – the API will be based on
generic interfaces:
public interface MapInterface<T>
{
void map(T item);
};
62
Custom Worker Threads
Three main classes:
QThreadPool
QThreadManager
QFutureInterface
63
QThreadPool
Threads are created when needed and expire when
not used.
Main API:
void start(QRunnable *)
Creating a QRunnable subclass:
class MyRunnable : public QRunnable
{
public: void run() { ... }
};
64
QThreadManager
Manages the maximum and active thread count
Manages a run queue.
Global: QThreadManager::globalThreadManager();
Two modes of use:
Update the thread count directly:
bool tryReserveThread()
void freeThread()
Use the run queue:
void start(QRunnable *, int priority = 0);
65
QFutureInterface
Back-end class for QFuture
Allows reporting
results
progress
finished
Checking status
canceled
paused (throttled)
66
Safety
Use pure functions:
Result depends on arguments only
No side-effects
Lock access to global data!
67
Example – creating a Task
Task: single threaded asynchronous foo
Easy way to use a QFutureInterface
Create function that takes a QFutureInterface
argument:
void foo(QFutureInteface &)
Pass this function to run()
void run(foo);
68
Task Example
void primes(QFutureInteface<int> &interface,
int begin, int end)
{
setProgressRange(begin, end);
int current = begin;
while (isCanceled() == false && current < end) {
if (isPrime(current))
reportResult(current);
setProgressValue(current);
++current;
}
}
QFuture<int> f = run(primes, begin, end);
69
Exceptions
Can be thrown across threads:
try {
map(images, foo);
} catch (Exception e) {
...
}
But there is a catch...
70
Exceptions – the catch:
Must inherit from QtConcurrent::Exception
Must implement two helper functions,
raise() and clone():
class MyException : public Exception
{
public:
void raise()
{ throw *this; }
Exception *clone()
{ return new MyException(*this); }
};
71
Producer / Consumer
Normally all results are stored in the QFuture
To minimize the use of temporary storage, you can
take he results:
Result<T> result = QFuture<T>::takeResult();
Gives you the first available result, if any.
bool Result::isValid()
int Result::index()
72
Efficient Producer / Consumer
If the results are light-weight, it might be more efficient
to batch them.
mapped() constructs light-weight results directly in a
QVector.
Result<QVector<T> > results = QFuture<T>::takeResults();
Matched by a corresponding signal:
QFutureWatcher::resultsReady();
73
Argument binding
Use boost::bind
Will also be a part of C++09
void map(QImage &image, int size)
{
image.scale(size, size);
}
QList<QImage> image;
map(image, bind(image, 100));
74
STL containers
Works just fine with Qt Concurrent.
Be aware that mapped, mappedReduced, filtered
copies the container.
Use iterators in this case:
vector<int> vector;
vector<int> result =
mapped(vec.begin(), vec.end(), foo):
75
QFutureSynchronizer
Provides automatic synchronization
Calls QFuture::waitForFinshed() in its destructor.
Useful when you have complex code and want to make
sure synchronization is done in all code paths.
void foo()
{
QFuture<void> future = ...
QFutureSynchronizer<void> sync(future);
if (...)
return;
}
76
Qt Concurrent vs OpenMP
QtConcurrent::map(list, foo);
//--------------
omp_set_num_threads(QThread::idealThreadCount());
#pragma omp parallel for
for (int i = 0, i < list.count() ++i) {
foo(list[i]);
}
77
Qt Concurrent vs TBB
QtConcurrent::map(list, foo);
//--------------
class ApplyFoo
{
List list;
public:
void operator()(const BLockedRange<size_t> &r) const {
for (size_t i = r.begin(); i != r.end() ++i)
foo(list[i]);
}
ApplyFoo(List &list)
: list(list) { }
};
ParallelFor(BlockedRange<size_t>(0, list.count()),
ApplyFoo(list), auto_partitioner());
78