Doc: Expand on thread synchronization details

- Introduce the concept of "mutual exclusion"
- Rewrite/add explanations on how synchronization happens and how to
  use these tools
- Remove similar content from the "Thread Basics" page
- Fix links to examples

Change-Id: Id008a8fc3f68bf242cae1704c5c8318149d908b4
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
Reviewed-by: Jerome Pasion <jerome.pasion@digia.com>
This commit is contained in:
Sze Howe Koh 2013-10-02 23:59:17 +08:00 committed by The Qt Project
parent 370b642092
commit dc6852ca63
2 changed files with 40 additions and 54 deletions

View File

@ -224,27 +224,11 @@
has terminated.
\endlist
\section2 Using a Mutex to Protect the Integrity of Data
\section2 Protecting the Integrity of Data
A mutex is an object that has \l{QMutex::}{lock()} and \l{QMutex::}{unlock()}
methods and remembers if it is already locked. A mutex is designed to be
called from multiple threads. \l{QMutex::}{lock()} returns immediately if
the mutex is not locked. The next call from another thread will find the
mutex in a locked state and then \l{QMutex::}{lock()} will block the thread
until the other thread calls \l{QMutex::}{unlock()}. This functionality can
make sure that a code section will be executed by only one thread at a time.
The following line sketches how a mutex can be used to make a method
thread-safe:
\code
void Worker::work()
{
this->mutex.lock(); // first thread can pass, other threads will be blocked here
doWork();
this->mutex.unlock();
}
\endcode
When writing a multithread application, extra care must be taken to avoid
data corruption. See \l{Synchronizing Threads} for a discussion on how to
use threads safely.
\section2 Dealing with Asynchronous Execution

View File

@ -290,51 +290,46 @@
\contentspage Thread Support in Qt
\nextpage Reentrancy and Thread-Safety
While the main idea
with threads is that they should be as concurrent as possible,
there are points where threads must stop and wait for other
threads. For example, if two threads try to access the same
global variable simultaneously, the results are usually
undefined.
While the purpose of threads is to allow code to run in parallel,
there are times where threads must stop and wait for other
threads. For example, if two threads try to write to the same
variable simultaneously, the result is undefined. The principle of
forcing threads to wait for one another is called \e{mutual exclusion}.
It is a common technique for protecting shared resources such as data.
Qt provides low-level primitives as well as high-level mechanisms
for synchronizing threads.
\section1 Low-Level Synchronization Primitives
QMutex provides a mutually exclusive lock, or mutex. At most one
thread can hold the mutex at any time. If a thread tries to
acquire the mutex while the mutex is already locked, the thread will
be put to sleep until the thread that currently holds the mutex
unlocks it. Mutexes are often used to protect accesses to shared
data (i.e., data that can be accessed from multiple threads
simultaneously). In the \l{Reentrancy and Thread-Safety} section
below, we will use it to make a class thread-safe.
QMutex is the basic class for enforcing mutual exclusion. A thread
locks a mutex in order to gain access to a shared resource. If a second
thread tries to lock the mutex while it is already locked, the second
thread will be put to sleep until the first thread completes its task
and unlocks the mutex.
QReadWriteLock is similar to QMutex, except that it distinguishes
between "read" and "write" access to shared data and allows
multiple readers to access the data simultaneously. Using
QReadWriteLock instead of QMutex when it is possible can make
multithreaded programs more concurrent.
between "read" and "write" access. When a piece of data is not being
written to, it is safe for multiple threads to read from it simultaneously.
A QMutex forces multiple readers to take turns to read shared data, but a
QReadWriteLock allows simultaneous reading, thus improving parallelism.
QSemaphore is a generalization of QMutex that protects a certain
number of identical resources. In contrast, a mutex protects
exactly one resource. The \l{threads/semaphores}{Semaphores}
example shows a typical application of semaphores: synchronizing
access to a circular buffer between a producer and a consumer.
number of identical resources. In contrast, a QMutex protects
exactly one resource. The \l{Semaphores Example} shows a typical application
of semaphores: synchronizing access to a circular buffer between a producer
and a consumer.
QWaitCondition allows a thread to wake up other threads when some
condition has been met. One or many threads can block waiting for
a QWaitCondition to set a condition with
\l{QWaitCondition::wakeOne()}{wakeOne()} or
\l{QWaitCondition::wakeAll()}{wakeAll()}. Use
\l{QWaitCondition::wakeOne()}{wakeOne()} to wake one randomly
selected event or \l{QWaitCondition::wakeAll()}{wakeAll()} to
wake them all. The \l{threads/waitconditions}{Wait Conditions}
example shows how to solve the producer-consumer problem using
QWaitCondition instead of QSemaphore.
QWaitCondition synchronizes threads not by enforcing mutual exclusion but by
providing a \e{condition variable}. While the other primitives make threads
wait until a resource is unlocked, QWaitCondition makes threads wait until a
particular condition has been met. To allow the waiting threads to proceed,
call \l{QWaitCondition::wakeOne()}{wakeOne()} to wake one randomly
selected thread or \l{QWaitCondition::wakeAll()}{wakeAll()} to wake them all
simultaneously. The \l{Wait Conditions Example} shows how to solve the
producer-consumer problem using QWaitCondition instead of QSemaphore.
Note that Qt's synchronization classes rely on the use of properly
\note Qt's synchronization classes rely on the use of properly
aligned pointers. For instance, you cannot use packed classes with
MSVC.
@ -381,7 +376,14 @@
system and runs the method immediately in the current thread.
There is no risk of deadlocks when using the event system for thread
synchronization, unlike using low-level primitives.
synchronization, unlike using low-level primitives. However, the event system
does not enforce mutual exclusion. If invokable methods access shared data,
they must still be protected with low-level primitives.
Having said that, Qt's event system, along with \l{Implicit Sharing}{implicitly
shared} data structures, offers an alternative to traditional thread locking.
If signals and slots are used exclusively and no variables are shared between
threads, a multithreaded program can do without low-level primitives altogether.
\sa QThread::exec(), {Threads and QObjects}
*/