qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,364 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example threads/mandelbrot
\title Mandelbrot Example
\ingroup qtconcurrent-mtexamples
\brief The Mandelbrot example demonstrates multi-thread programming
using Qt. It shows how to use a worker thread to
perform heavy computations without blocking the main thread's
event loop.
\image mandelbrot-example.png Screenshot of the Mandelbrot example
The heavy computation here is the Mandelbrot set, probably the world's most
famous fractal. These days, while sophisticated programs, such as
\l{https://xaos-project.github.io/}{XaoS}, provide real-time zooming in
the Mandelbrot set, the standard Mandelbrot algorithm is just slow enough
for our purposes.
In real life, the approach described here is applicable to a
large set of problems, including synchronous network I/O and
database access, where the user interface must remain responsive
while some heavy operation is taking place. The \l
{Blocking Fortune Client} example shows the same principle at
work in a TCP client.
The Mandelbrot application supports zooming and scrolling using
the mouse or the keyboard. To avoid freezing the main thread's
event loop (and, as a consequence, the application's user
interface), we put all the fractal computation in a separate
worker thread. The thread emits a signal when it is done
rendering the fractal.
During the time where the worker thread is recomputing the
fractal to reflect the new zoom factor position, the main thread
simply scales the previously rendered pixmap to provide immediate
feedback. The result doesn't look as good as what the worker
thread eventually ends up providing, but at least it makes the
application more responsive. The sequence of screenshots below
shows the original image, the scaled image, and the rerendered
image.
\table
\row
\li \inlineimage mandelbrot_zoom1.png
\li \inlineimage mandelbrot_zoom2.png
\li \inlineimage mandelbrot_zoom3.png
\endtable
Similarly, when the user scrolls, the previous pixmap is scrolled
immediately, revealing unpainted areas beyond the edge of the
pixmap, while the image is rendered by the worker thread.
\table
\row
\li \inlineimage mandelbrot_scroll1.png
\li \inlineimage mandelbrot_scroll2.png
\li \inlineimage mandelbrot_scroll3.png
\endtable
The application consists of two classes:
\list
\li \c RenderThread is a QThread subclass that renders
the Mandelbrot set.
\li \c MandelbrotWidget is a QWidget subclass that shows the
Mandelbrot set on screen and lets the user zoom and scroll.
\endlist
If you are not already familiar with Qt's thread support, we
recommend that you start by reading the \l{Thread Support in Qt}
overview.
\section1 RenderThread Class Definition
We'll start with the definition of the \c RenderThread class:
\snippet threads/mandelbrot/renderthread.h 0
The class inherits QThread so that it gains the ability to run in
a separate thread. Apart from the constructor and destructor, \c
render() is the only public function. Whenever the thread is done
rendering an image, it emits the \c renderedImage() signal.
The protected \c run() function is reimplemented from QThread. It
is automatically called when the thread is started.
In the \c private section, we have a QMutex, a QWaitCondition,
and a few other data members. The mutex protects the other data
member.
\section1 RenderThread Class Implementation
\snippet threads/mandelbrot/renderthread.cpp 0
In the constructor, we initialize the \c restart and \c abort
variables to \c false. These variables control the flow of the \c
run() function.
We also initialize the \c colormap array, which contains a series
of RGB colors.
\snippet threads/mandelbrot/renderthread.cpp 1
The destructor can be called at any point while the thread is
active. We set \c abort to \c true to tell \c run() to stop
running as soon as possible. We also call
QWaitCondition::wakeOne() to wake up the thread if it's sleeping.
(As we will see when we review \c run(), the thread is put to
sleep when it has nothing to do.)
The important thing to notice here is that \c run() is executed
in its own thread (the worker thread), whereas the \c
RenderThread constructor and destructor (as well as the \c
render() function) are called by the thread that created the
worker thread. Therefore, we need a mutex to protect accesses to
the \c abort and \c condition variables, which might be accessed
at any time by \c run().
At the end of the destructor, we call QThread::wait() to wait
until \c run() has exited before the base class destructor is
invoked.
\snippet threads/mandelbrot/renderthread.cpp 2
The \c render() function is called by the \c MandelbrotWidget
whenever it needs to generate a new image of the Mandelbrot set.
The \c centerX, \c centerY, and \c scaleFactor parameters specify
the portion of the fractal to render; \c resultSize specifies the
size of the resulting QImage.
The function stores the parameters in member variables. If the
thread isn't already running, it starts it; otherwise, it sets \c
restart to \c true (telling \c run() to stop any unfinished
computation and start again with the new parameters) and wakes up
the thread, which might be sleeping.
\snippet threads/mandelbrot/renderthread.cpp 3
\c run() is quite a big function, so we'll break it down into
parts.
The function body is an infinite loop which starts by storing the
rendering parameters in local variables. As usual, we protect
accesses to the member variables using the class's mutex. Storing
the member variables in local variables allows us to minimize the
amount of code that needs to be protected by a mutex. This ensures
that the main thread will never have to block for too long when
it needs to access \c{RenderThread}'s member variables (e.g., in
\c render()).
The \c forever keyword is, like \c foreach, a Qt pseudo-keyword.
\snippet threads/mandelbrot/renderthread.cpp 4
\snippet threads/mandelbrot/renderthread.cpp 5
\snippet threads/mandelbrot/renderthread.cpp 6
\snippet threads/mandelbrot/renderthread.cpp 7
Then comes the core of the algorithm. Instead of trying to create
a perfect Mandelbrot set image, we do multiple passes and
generate more and more precise (and computationally expensive)
approximations of the fractal.
We create a high resolution pixmap by applying the device
pixel ratio to the target size (see
\l{Drawing High Resolution Versions of Pixmaps and Images}).
If we discover inside the loop that \c restart has been set to \c
true (by \c render()), we break out of the loop immediately, so
that the control quickly returns to the very top of the outer
loop (the \c forever loop) and we fetch the new rendering
parameters. Similarly, if we discover that \c abort has been set
to \c true (by the \c RenderThread destructor), we return from
the function immediately, terminating the thread.
The core algorithm is beyond the scope of this tutorial.
\snippet threads/mandelbrot/renderthread.cpp 8
\snippet threads/mandelbrot/renderthread.cpp 9
Once we're done with all the iterations, we call
QWaitCondition::wait() to put the thread to sleep,
unless \c restart is \c true. There's no use in keeping a worker
thread looping indefinitely while there's nothing to do.
\snippet threads/mandelbrot/renderthread.cpp 10
The \c rgbFromWaveLength() function is a helper function that
converts a wave length to a RGB value compatible with 32-bit
\l{QImage}s. It is called from the constructor to initialize the
\c colormap array with pleasing colors.
\section1 MandelbrotWidget Class Definition
The \c MandelbrotWidget class uses \c RenderThread to draw the
Mandelbrot set on screen. Here's the class definition:
\snippet threads/mandelbrot/mandelbrotwidget.h 0
The widget reimplements many event handlers from QWidget. In
addition, it has an \c updatePixmap() slot that we'll connect to
the worker thread's \c renderedImage() signal to update the
display whenever we receive new data from the thread.
Among the private variables, we have \c thread of type \c
RenderThread and \c pixmap, which contains the last rendered
image.
\section1 MandelbrotWidget Class Implementation
\snippet threads/mandelbrot/mandelbrotwidget.cpp 0
The implementation starts with a few constants that we'll need
later on.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 1
The interesting part of the constructor is the
QObject::connect() call.
Although it looks like a standard signal-slot connection between
two \l{QObject}s, because the signal is emitted in a different
thread than the receiver lives in, the connection is effectively a
\l{Qt::QueuedConnection}{queued connection}. These connections are
asynchronous (i.e., non-blocking), meaning that the slot will be
called at some point after the \c emit statement. What's more, the
slot will be invoked in the thread in which the receiver lives.
Here, the signal is emitted in the worker thread, and the slot is
executed in the GUI thread when control returns to the event loop.
With queued connections, Qt must store a copy of the arguments
that were passed to the signal so that it can pass them to the
slot later on. Qt knows how to take of copy of many C++ and Qt
types, so, no further action is needed for QImage.
If a custom type was used, a call to the template function
qRegisterMetaType() would be required before the type
could be used as a parameter in queued connections.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 2
\snippet threads/mandelbrot/mandelbrotwidget.cpp 3
\snippet threads/mandelbrot/mandelbrotwidget.cpp 4
In \l{QWidget::paintEvent()}{paintEvent()}, we start by filling
the background with black. If we have nothing to paint yet (\c
pixmap is null), we display a message on the widget asking the user
to be patient and return from the function immediately.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 5
\snippet threads/mandelbrot/mandelbrotwidget.cpp 6
\snippet threads/mandelbrot/mandelbrotwidget.cpp 7
\snippet threads/mandelbrot/mandelbrotwidget.cpp 8
If the pixmap has the right scale factor, we draw the pixmap directly onto
the widget.
Otherwise, we create a preview pixmap to be shown until the calculation
finishes and translate the \l{Coordinate System}{coordinate system}
accordingly.
Since we are going to use transformations on the painter
and use an overload of QPainter::drawPixmap() that does not support
high resolution pixmaps in that case, we create a pixmap with device pixel
ratio 1.
By reverse mapping the widget's rectangle using the scaled painter matrix,
we also make sure that only the exposed areas of the pixmap are drawn.
The calls to QPainter::save() and QPainter::restore() make sure that any
painting performed afterwards uses the standard coordinate system.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 9
At the end of the paint event handler, we draw a text string and
a semi-transparent rectangle on top of the fractal.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 10
Whenever the user resizes the widget, we call \c render() to
start generating a new image, with the same \c centerX, \c
centerY, and \c curScale parameters but with the new widget size.
Notice that we rely on \c resizeEvent() being automatically
called by Qt when the widget is shown the first time to generate
the initial image.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 11
The key press event handler provides a few keyboard bindings for
the benefit of users who don't have a mouse. The \c zoom() and \c
scroll() functions will be covered later.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 12
The wheel event handler is reimplemented to make the mouse wheel
control the zoom level. QWheelEvent::angleDelta() returns the angle
of the wheel mouse movement, in eighths of a degree. For most mice,
one wheel step corresponds to 15 degrees. We find out how many
mouse steps we have and determine the resulting zoom factor.
For example, if we have two wheel steps in the positive direction
(i.e., +30 degrees), the zoom factor becomes \c ZoomInFactor
to the second power, i.e. 0.8 * 0.8 = 0.64.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 13
Pinch to zoom has been implemented with QGesture as outlined in
\l{Gestures in Widgets and Graphics View}.
\snippet threads/mandelbrot/mandelbrotwidget.cpp gesture1
When the user presses the left mouse button, we store the mouse
pointer position in \c lastDragPos.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 14
When the user moves the mouse pointer while the left mouse button
is pressed, we adjust \c pixmapOffset to paint the pixmap at a
shifted position and call QWidget::update() to force a repaint.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 15
When the left mouse button is released, we update \c pixmapOffset
just like we did on a mouse move and we reset \c lastDragPos to a
default value. Then, we call \c scroll() to render a new image
for the new position. (Adjusting \c pixmapOffset isn't sufficient
because areas revealed when dragging the pixmap are drawn in
black.)
\snippet threads/mandelbrot/mandelbrotwidget.cpp 16
The \c updatePixmap() slot is invoked when the worker thread has
finished rendering an image. We start by checking whether a drag
is in effect and do nothing in that case. In the normal case, we
store the image in \c pixmap and reinitialize some of the other
members. At the end, we call QWidget::update() to refresh the
display.
At this point, you might wonder why we use a QImage for the
parameter and a QPixmap for the data member. Why not stick to one
type? The reason is that QImage is the only class that supports
direct pixel manipulation, which we need in the worker thread. On
the other hand, before an image can be drawn on screen, it must
be converted into a pixmap. It's better to do the conversion once
and for all here, rather than in \c paintEvent().
\snippet threads/mandelbrot/mandelbrotwidget.cpp 17
In \c zoom(), we recompute \c curScale. Then we call
QWidget::update() to draw a scaled pixmap, and we ask the worker
thread to render a new image corresponding to the new \c curScale
value.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 18
\c scroll() is similar to \c zoom(), except that the affected
parameters are \c centerX and \c centerY.
\section1 The main() Function
The application's multithreaded nature has no impact on its \c
main() function, which is as simple as usual:
\snippet threads/mandelbrot/main.cpp 0
*/

View File

@ -0,0 +1,142 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example threads/queuedcustomtype
\title Queued Custom Type Example
\brief Demonstrates multi-thread programming using Qt.
\ingroup qtconcurrent-mtexamples
\brief The Queued Custom Type example shows how to send custom types between
threads with queued signals and slots.
\image queuedcustomtype-example.png
Contents:
\tableofcontents
\section1 Overview
In the \l{Custom Type Example}, we showed how to integrate custom types with
the meta-object system, enabling them to be stored in QVariant objects, written
out in debugging information and used in signal-slot communication.
In this example, we create a new value class, \c Block, and register it
with the meta-object system to enable us to send instances of it between
threads using queued signals and slots.
\section1 The Block Class
The \c Block class is similar to the \c Message class described in the
\l{Custom Type Example}. It provides the default constructor, copy
constructor and destructor in the public section of the class that the
meta-object system requires. It describes a colored rectangle.
\snippet threads/queuedcustomtype/block.h custom type definition and meta-type declaration
We will still need to register it with the meta-object system at
run-time by calling the qRegisterMetaType() template function before
we make any signal-slot connections that use this type.
Even though we do not intend to use the type with QVariant in this example,
it is good practice to also declare the new type with Q_DECLARE_METATYPE().
The implementation of the \c Block class is trivial, so we avoid quoting
it here.
\section1 The Window Class
We define a simple \c Window class with a public slot that accepts a
\c Block object. The rest of the class is concerned with managing the
user interface and handling images.
\snippet threads/queuedcustomtype/window.h Window class definition
The \c Window class also contains a worker thread, provided by a
\c RenderThread object. This will emit signals to send \c Block objects
to the window's \c addBlock(Block) slot.
The parts of the \c Window class that are most relevant are the constructor
and the \c addBlock(Block) slot.
The constructor creates a thread for rendering images, sets up a user
interface containing a label and two push buttons that are connected to
slots in the same class.
\snippet threads/queuedcustomtype/window.cpp Window constructor start
\snippet threads/queuedcustomtype/window.cpp set up widgets and connections
\snippet threads/queuedcustomtype/window.cpp connecting signal with custom type
In the last of these connections, we connect a signal in the
\c RenderThread object to the \c addBlock(Block) slot in the window.
\dots
\snippet threads/queuedcustomtype/window.cpp Window constructor finish
The rest of the constructor simply sets up the layout of the window.
The \c addBlock(Block) slot receives blocks from the rendering thread via
the signal-slot connection set up in the constructor:
\snippet threads/queuedcustomtype/window.cpp Adding blocks to the display
We simply paint these onto the label as they arrive.
\section1 The RenderThread Class
The \c RenderThread class processes an image, creating \c Block objects
and using the \c sendBlock(Block) signal to send them to other components
in the example.
\snippet threads/queuedcustomtype/renderthread.h RenderThread class definition
The constructor and destructor are not quoted here. These take care of
setting up the thread's internal state and cleaning up when it is destroyed.
Processing is started with the \c processImage() function, which calls the
\c RenderThread class's reimplementation of the QThread::run() function:
\snippet threads/queuedcustomtype/renderthread.cpp processing the image (start)
Ignoring the details of the way the image is processed, we see that the
signal containing a block is emitted in the usual way:
\dots
\snippet threads/queuedcustomtype/renderthread.cpp processing the image (finish)
Each signal that is emitted will be queued and delivered later to the
window's \c addBlock(Block) slot.
\section1 Registering the Type
In the example's \c{main()} function, we perform the registration of the
\c Block class as a custom type with the meta-object system by calling the
qRegisterMetaType() template function:
\snippet threads/queuedcustomtype/main.cpp main function
This call is placed here to ensure that the type is registered before any
signal-slot connections are made that use it.
The rest of the \c{main()} function is concerned with setting a seed for
the pseudo-random number generator, creating and showing the window, and
setting a default image. See the source code for the implementation of the
\c createImage() function.
\section1 Further Reading
This example showed how a custom type can be registered with the
meta-object system so that it can be used with signal-slot connections
between threads. For ordinary communication involving direct signals and
slots, it is enough to simply declare the type in the way described in the
\l{Custom Type Example}.
In practice, both the Q_DECLARE_METATYPE() macro and the qRegisterMetaType()
template function can be used to register custom types, but
qRegisterMetaType() is only required if you need to perform signal-slot
communication or need to create and destroy objects of the custom type
at run-time.
More information on using custom types with Qt can be found in the
\l{Creating Custom Qt Types} document.
*/

View File

@ -0,0 +1,123 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example threads/semaphores
\title Semaphores Example
\brief Demonstrates multi-thread programming using Qt.
\ingroup qtconcurrent-mtexamples
\brief The Semaphores example shows how to use QSemaphore to control
access to a circular buffer shared by a producer thread and a
consumer thread.
The producer writes data to the buffer until it reaches the end
of the buffer, at which point it restarts from the beginning,
overwriting existing data. The consumer thread reads the data as
it is produced and writes it to standard error.
Semaphores make it possible to have a higher level of concurrency
than mutexes. If accesses to the buffer were guarded by a QMutex,
the consumer thread couldn't access the buffer at the same time
as the producer thread. Yet, there is no harm in having both
threads working on \e{different parts} of the buffer at the same
time.
The example comprises two classes: \c Producer and \c Consumer.
Both inherit from QThread. The circular buffer used for
communicating between these two classes and the semaphores that
protect it are global variables.
An alternative to using QSemaphore to solve the producer-consumer
problem is to use QWaitCondition and QMutex. This is what the
\l{Wait Conditions Example} does.
\section1 Global Variables
Let's start by reviewing the circular buffer and the associated
semaphores:
\snippet threads/semaphores/semaphores.cpp 0
\c DataSize is the amount of data that the producer will generate.
To keep the example as simple as possible, we make it a constant.
\c BufferSize is the size of the circular buffer. It is less than
\c DataSize, meaning that at some point the producer will reach
the end of the buffer and restart from the beginning.
To synchronize the producer and the consumer, we need two
semaphores. The \c freeBytes semaphore controls the "free" area
of the buffer (the area that the producer hasn't filled with data
yet or that the consumer has already read). The \c usedBytes
semaphore controls the "used" area of the buffer (the area that
the producer has filled but that the consumer hasn't read yet).
Together, the semaphores ensure that the producer is never more
than \c BufferSize bytes ahead of the consumer, and that the
consumer never reads data that the producer hasn't generated yet.
The \c freeBytes semaphore is initialized with \c BufferSize,
because initially the entire buffer is empty. The \c usedBytes
semaphore is initialized to 0 (the default value if none is
specified).
\section1 Producer Class
Let's review the code for the \c Producer class:
\snippet threads/semaphores/semaphores.cpp 1
\snippet threads/semaphores/semaphores.cpp 2
The producer generates \c DataSize bytes of data. Before it
writes a byte to the circular buffer, it must acquire a "free"
byte using the \c freeBytes semaphore. The QSemaphore::acquire()
call might block if the consumer hasn't kept up the pace with the
producer.
At the end, the producer releases a byte using the \c usedBytes
semaphore. The "free" byte has successfully been transformed into
a "used" byte, ready to be read by the consumer.
\section1 Consumer Class
Let's now turn to the \c Consumer class:
\snippet threads/semaphores/semaphores.cpp 3
\snippet threads/semaphores/semaphores.cpp 4
The code is very similar to the producer, except that this time
we acquire a "used" byte and release a "free" byte, instead of
the opposite.
\section1 The main() Function
In \c main(), we create the two threads and call QThread::wait()
to ensure that both threads get time to finish before we exit:
\snippet threads/semaphores/semaphores.cpp 5
\snippet threads/semaphores/semaphores.cpp 6
So what happens when we run the program? Initially, the producer
thread is the only one that can do anything; the consumer is
blocked waiting for the \c usedBytes semaphore to be released (its
initial \l{QSemaphore::available()}{available()} count is 0).
Once the producer has put one byte in the buffer,
\c{freeBytes.available()} is \c BufferSize - 1 and
\c{usedBytes.available()} is 1. At that point, two things can
happen: Either the consumer thread takes over and reads that
byte, or the producer thread gets to produce a second byte.
The producer-consumer model presented in this example makes it
possible to write highly concurrent multithreaded applications.
On a multiprocessor machine, the program is potentially up to
twice as fast as the equivalent mutex-based program, since the
two threads can be active at the same time on different parts of
the buffer.
Be aware though that these benefits aren't always realized.
Acquiring and releasing a QSemaphore has a cost. In practice, it
would probably be worthwhile to divide the buffer into chunks and
to operate on chunks instead of individual bytes. The buffer size
is also a parameter that must be selected carefully, based on
experimentation.
*/

View File

@ -0,0 +1,130 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example threads/waitconditions
\title Wait Conditions Example
\brief Demonstrates multi-thread programming using Qt.
\ingroup qtconcurrent-mtexamples
\brief The Wait Conditions example shows how to use QWaitCondition and
QMutex to control access to a circular buffer shared by a
producer thread and a consumer thread.
The producer writes data to the buffer until it reaches the end
of the buffer, at which point it restarts from the beginning,
overwriting existing data. The consumer thread reads the data as
it is produced and writes it to standard error.
Wait conditions make it possible to have a higher level of
concurrency than what is possible with mutexes alone. If accesses
to the buffer were simply guarded by a QMutex, the consumer
thread couldn't access the buffer at the same time as the
producer thread. Yet, there is no harm in having both threads
working on \e{different parts} of the buffer at the same time.
The example comprises two classes: \c Producer and \c Consumer.
Both inherit from QThread. The circular buffer used for
communicating between these two classes and the synchronization
tools that protect it are global variables.
An alternative to using QWaitCondition and QMutex to solve the
producer-consumer problem is to use QSemaphore. This is what the
\l{Semaphores Example} does.
\section1 Global Variables
Let's start by reviewing the circular buffer and the associated
synchronization tools:
\snippet threads/waitconditions/waitconditions.cpp 0
\c DataSize is the amount of data that the producer will generate.
To keep the example as simple as possible, we make it a constant.
\c BufferSize is the size of the circular buffer. It is less than
\c DataSize, meaning that at some point the producer will reach
the end of the buffer and restart from the beginning.
To synchronize the producer and the consumer, we need two wait
conditions and one mutex. The \c bufferNotEmpty condition is
signalled when the producer has generated some data, telling the
consumer that it can start reading it. The \c bufferNotFull
condition is signalled when the consumer has read some data,
telling the producer that it can generate more. The \c numUsedBytes
is the number of bytes in the buffer that contain data.
Together, the wait conditions, the mutex, and the \c numUsedBytes
counter ensure that the producer is never more than \c BufferSize
bytes ahead of the consumer, and that the consumer never reads
data that the producer hasn't generated yet.
\section1 Producer Class
Let's review the code for the \c Producer class:
\snippet threads/waitconditions/waitconditions.cpp 1
\snippet threads/waitconditions/waitconditions.cpp 2
The producer generates \c DataSize bytes of data. Before it
writes a byte to the circular buffer, it must first check whether
the buffer is full (i.e., \c numUsedBytes equals \c BufferSize).
If the buffer is full, the thread waits on the \c bufferNotFull
condition.
At the end, the producer increments \c numUsedBytes and signalls
that the condition \c bufferNotEmpty is true, since \c
numUsedBytes is necessarily greater than 0.
We guard all accesses to the \c numUsedBytes variable with a
mutex. In addition, the QWaitCondition::wait() function accepts a
mutex as its argument. This mutex is unlocked before the thread
is put to sleep and locked when the thread wakes up. Furthermore,
the transition from the locked state to the wait state is atomic,
to prevent race conditions from occurring.
\section1 Consumer Class
Let's turn to the \c Consumer class:
\snippet threads/waitconditions/waitconditions.cpp 3
\snippet threads/waitconditions/waitconditions.cpp 4
The code is very similar to the producer. Before we read the
byte, we check whether the buffer is empty (\c numUsedBytes is 0)
instead of whether it's full and wait on the \c bufferNotEmpty
condition if it's empty. After we've read the byte, we decrement
\c numUsedBytes (instead of incrementing it), and we signal the
\c bufferNotFull condition (instead of the \c bufferNotEmpty
condition).
\section1 The main() Function
In \c main(), we create the two threads and call QThread::wait()
to ensure that both threads get time to finish before we exit:
\snippet threads/waitconditions/waitconditions.cpp 5
\snippet threads/waitconditions/waitconditions.cpp 6
So what happens when we run the program? Initially, the producer
thread is the only one that can do anything; the consumer is
blocked waiting for the \c bufferNotEmpty condition to be
signalled (\c numUsedBytes is 0). Once the producer has put one
byte in the buffer, \c numUsedBytes is strictly greater than 0, and the
\c bufferNotEmpty condition is signalled. At that point, two
things can happen: Either the consumer thread takes over and
reads that byte, or the producer gets to produce a second byte.
The producer-consumer model presented in this example makes it
possible to write highly concurrent multithreaded applications.
On a multiprocessor machine, the program is potentially up to
twice as fast as the equivalent mutex-based program, since the
two threads can be active at the same time on different parts of
the buffer.
Be aware though that these benefits aren't always realized.
Locking and unlocking a QMutex has a cost. In practice, it would
probably be worthwhile to divide the buffer into chunks and to
operate on chunks instead of individual bytes. The buffer size is
also a parameter that must be selected carefully, based on
experimentation.
*/