0% found this document useful (0 votes)
399 views78 pages

DevDays2007 - QT - Concurrent

This document provides an overview of Qt Concurrent, a framework for parallel programming in Qt. It discusses using Qt Concurrent to run functions asynchronously using threads from a thread pool. It also describes how to scale tasks across multiple CPU cores using APIs like map(), mapped(), and mappedReduced() to parallelize work on containers of data. The document outlines the asynchronous and parallel aspects of Qt Concurrent and how QFuture and QFutureWatcher can be used for synchronization and receiving notifications.

Uploaded by

Milan Bjekic
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
399 views78 pages

DevDays2007 - QT - Concurrent

This document provides an overview of Qt Concurrent, a framework for parallel programming in Qt. It discusses using Qt Concurrent to run functions asynchronously using threads from a thread pool. It also describes how to scale tasks across multiple CPU cores using APIs like map(), mapped(), and mappedReduced() to parallelize work on containers of data. The document outlines the asynchronous and parallel aspects of Qt Concurrent and how QFuture and QFutureWatcher can be used for synchronization and receiving notifications.

Uploaded by

Milan Bjekic
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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

You might also like