Sie sind auf Seite 1von 78

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