summaryrefslogtreecommitdiffstats
path: root/src/corelib/thread/qmutex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/thread/qmutex.cpp')
-rw-r--r--src/corelib/thread/qmutex.cpp515
1 files changed, 515 insertions, 0 deletions
diff --git a/src/corelib/thread/qmutex.cpp b/src/corelib/thread/qmutex.cpp
new file mode 100644
index 0000000000..001506cf7d
--- /dev/null
+++ b/src/corelib/thread/qmutex.cpp
@@ -0,0 +1,515 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qplatformdefs.h"
+#include "qmutex.h"
+#include <qdebug.h>
+
+#ifndef QT_NO_THREAD
+#include "qatomic.h"
+#include "qelapsedtimer.h"
+#include "qthread.h"
+#include "qmutex_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QMutex
+ \brief The QMutex class provides access serialization between threads.
+
+ \threadsafe
+
+ \ingroup thread
+
+ The purpose of a QMutex is to protect an object, data structure or
+ section of code so that only one thread can access it at a time
+ (this is similar to the Java \c synchronized keyword). It is
+ usually best to use a mutex with a QMutexLocker since this makes
+ it easy to ensure that locking and unlocking are performed
+ consistently.
+
+ For example, say there is a method that prints a message to the
+ user on two lines:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 0
+
+ If these two methods are called in succession, the following happens:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 1
+
+ If these two methods are called simultaneously from two threads then the
+ following sequence could result:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 2
+
+ If we add a mutex, we should get the result we want:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 3
+
+ Then only one thread can modify \c number at any given time and
+ the result is correct. This is a trivial example, of course, but
+ applies to any other case where things need to happen in a
+ particular sequence.
+
+ When you call lock() in a thread, other threads that try to call
+ lock() in the same place will block until the thread that got the
+ lock calls unlock(). A non-blocking alternative to lock() is
+ tryLock().
+
+ \sa QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition
+*/
+
+/*!
+ \enum QMutex::RecursionMode
+
+ \value Recursive In this mode, a thread can lock the same mutex
+ multiple times and the mutex won't be unlocked
+ until a corresponding number of unlock() calls
+ have been made.
+
+ \value NonRecursive In this mode, a thread may only lock a mutex
+ once.
+
+ \sa QMutex()
+*/
+
+/*!
+ Constructs a new mutex. The mutex is created in an unlocked state.
+
+ If \a mode is QMutex::Recursive, a thread can lock the same mutex
+ multiple times and the mutex won't be unlocked until a
+ corresponding number of unlock() calls have been made. The
+ default is QMutex::NonRecursive.
+
+ \sa lock(), unlock()
+*/
+QMutex::QMutex(RecursionMode mode)
+ : d(new QMutexPrivate(mode))
+{ }
+
+/*!
+ Destroys the mutex.
+
+ \warning Destroying a locked mutex may result in undefined behavior.
+*/
+QMutex::~QMutex()
+{ delete static_cast<QMutexPrivate *>(d); }
+
+/*!
+ Locks the mutex. If another thread has locked the mutex then this
+ call will block until that thread has unlocked it.
+
+ Calling this function multiple times on the same mutex from the
+ same thread is allowed if this mutex is a
+ \l{QMutex::Recursive}{recursive mutex}. If this mutex is a
+ \l{QMutex::NonRecursive}{non-recursive mutex}, this function will
+ \e dead-lock when the mutex is locked recursively.
+
+ \sa unlock()
+*/
+void QMutex::lock()
+{
+ QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
+ Qt::HANDLE self;
+
+ if (d->recursive) {
+ self = QThread::currentThreadId();
+ if (d->owner == self) {
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::lock", "Overflow in recursion counter");
+ return;
+ }
+
+ bool isLocked = d->contenders.testAndSetAcquire(0, 1);
+ if (!isLocked) {
+ // didn't get the lock, wait for it
+ isLocked = d->wait();
+ Q_ASSERT_X(isLocked, "QMutex::lock",
+ "Internal error, infinite wait has timed out.");
+ }
+
+ d->owner = self;
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::lock", "Overflow in recursion counter");
+ return;
+ }
+
+ bool isLocked = d->contenders.testAndSetAcquire(0, 1);
+ if (!isLocked) {
+ lockInternal();
+ }
+}
+
+/*!
+ Attempts to lock the mutex. If the lock was obtained, this function
+ returns true. If another thread has locked the mutex, this
+ function returns false immediately.
+
+ If the lock was obtained, the mutex must be unlocked with unlock()
+ before another thread can successfully lock it.
+
+ Calling this function multiple times on the same mutex from the
+ same thread is allowed if this mutex is a
+ \l{QMutex::Recursive}{recursive mutex}. If this mutex is a
+ \l{QMutex::NonRecursive}{non-recursive mutex}, this function will
+ \e always return false when attempting to lock the mutex
+ recursively.
+
+ \sa lock(), unlock()
+*/
+bool QMutex::tryLock()
+{
+ QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
+ Qt::HANDLE self;
+
+ if (d->recursive) {
+ self = QThread::currentThreadId();
+ if (d->owner == self) {
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter");
+ return true;
+ }
+
+ bool isLocked = d->contenders.testAndSetAcquire(0, 1);
+ if (!isLocked) {
+ // some other thread has the mutex locked, or we tried to
+ // recursively lock an non-recursive mutex
+ return isLocked;
+ }
+
+ d->owner = self;
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter");
+ return isLocked;
+ }
+
+ return d->contenders.testAndSetAcquire(0, 1);
+}
+
+/*! \overload
+
+ Attempts to lock the mutex. This function returns true if the lock
+ was obtained; otherwise it returns false. If another thread has
+ locked the mutex, this function will wait for at most \a timeout
+ milliseconds for the mutex to become available.
+
+ Note: Passing a negative number as the \a timeout is equivalent to
+ calling lock(), i.e. this function will wait forever until mutex
+ can be locked if \a timeout is negative.
+
+ If the lock was obtained, the mutex must be unlocked with unlock()
+ before another thread can successfully lock it.
+
+ Calling this function multiple times on the same mutex from the
+ same thread is allowed if this mutex is a
+ \l{QMutex::Recursive}{recursive mutex}. If this mutex is a
+ \l{QMutex::NonRecursive}{non-recursive mutex}, this function will
+ \e always return false when attempting to lock the mutex
+ recursively.
+
+ \sa lock(), unlock()
+*/
+bool QMutex::tryLock(int timeout)
+{
+ QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
+ Qt::HANDLE self;
+
+ if (d->recursive) {
+ self = QThread::currentThreadId();
+ if (d->owner == self) {
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter");
+ return true;
+ }
+
+ bool isLocked = d->contenders.testAndSetAcquire(0, 1);
+ if (!isLocked) {
+ // didn't get the lock, wait for it
+ isLocked = d->wait(timeout);
+ if (!isLocked)
+ return false;
+ }
+
+ d->owner = self;
+ ++d->count;
+ Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter");
+ return true;
+ }
+
+ return (d->contenders.testAndSetAcquire(0, 1)
+ // didn't get the lock, wait for it
+ || d->wait(timeout));
+}
+
+
+/*!
+ Unlocks the mutex. Attempting to unlock a mutex in a different
+ thread to the one that locked it results in an error. Unlocking a
+ mutex that is not locked results in undefined behavior.
+
+ \sa lock()
+*/
+void QMutex::unlock()
+{
+ QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
+ if (d->recursive) {
+ if (!--d->count) {
+ d->owner = 0;
+ if (!d->contenders.testAndSetRelease(1, 0))
+ d->wakeUp();
+ }
+ } else {
+ if (!d->contenders.testAndSetRelease(1, 0))
+ d->wakeUp();
+ }
+}
+
+/*!
+ \fn bool QMutex::locked()
+
+ Returns true if the mutex is locked by another thread; otherwise
+ returns false.
+
+ It is generally a bad idea to use this function, because code
+ that uses it has a race condition. Use tryLock() and unlock()
+ instead.
+
+ \oldcode
+ bool isLocked = mutex.locked();
+ \newcode
+ bool isLocked = true;
+ if (mutex.tryLock()) {
+ mutex.unlock();
+ isLocked = false;
+ }
+ \endcode
+*/
+
+/*!
+ \class QMutexLocker
+ \brief The QMutexLocker class is a convenience class that simplifies
+ locking and unlocking mutexes.
+
+ \threadsafe
+
+ \ingroup thread
+
+ Locking and unlocking a QMutex in complex functions and
+ statements or in exception handling code is error-prone and
+ difficult to debug. QMutexLocker can be used in such situations
+ to ensure that the state of the mutex is always well-defined.
+
+ QMutexLocker should be created within a function where a
+ QMutex needs to be locked. The mutex is locked when QMutexLocker
+ is created. You can unlock and relock the mutex with \c unlock()
+ and \c relock(). If locked, the mutex will be unlocked when the
+ QMutexLocker is destroyed.
+
+ For example, this complex function locks a QMutex upon entering
+ the function and unlocks the mutex at all the exit points:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 4
+
+ This example function will get more complicated as it is
+ developed, which increases the likelihood that errors will occur.
+
+ Using QMutexLocker greatly simplifies the code, and makes it more
+ readable:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 5
+
+ Now, the mutex will always be unlocked when the QMutexLocker
+ object is destroyed (when the function returns since \c locker is
+ an auto variable).
+
+ The same principle applies to code that throws and catches
+ exceptions. An exception that is not caught in the function that
+ has locked the mutex has no way of unlocking the mutex before the
+ exception is passed up the stack to the calling function.
+
+ QMutexLocker also provides a \c mutex() member function that returns
+ the mutex on which the QMutexLocker is operating. This is useful
+ for code that needs access to the mutex, such as
+ QWaitCondition::wait(). For example:
+
+ \snippet doc/src/snippets/code/src_corelib_thread_qmutex.cpp 6
+
+ \sa QReadLocker, QWriteLocker, QMutex
+*/
+
+/*!
+ \fn QMutexLocker::QMutexLocker(QMutex *mutex)
+
+ Constructs a QMutexLocker and locks \a mutex. The mutex will be
+ unlocked when the QMutexLocker is destroyed. If \a mutex is zero,
+ QMutexLocker does nothing.
+
+ \sa QMutex::lock()
+*/
+
+/*!
+ \fn QMutexLocker::~QMutexLocker()
+
+ Destroys the QMutexLocker and unlocks the mutex that was locked
+ in the constructor.
+
+ \sa QMutex::unlock()
+*/
+
+/*!
+ \fn QMutex *QMutexLocker::mutex() const
+
+ Returns a pointer to the mutex that was locked in the
+ constructor.
+*/
+
+/*!
+ \fn void QMutexLocker::unlock()
+
+ Unlocks this mutex locker. You can use \c relock() to lock
+ it again. It does not need to be locked when destroyed.
+
+ \sa relock()
+*/
+
+/*!
+ \fn void QMutexLocker::relock()
+
+ Relocks an unlocked mutex locker.
+
+ \sa unlock()
+*/
+
+/*!
+ \fn QMutex::QMutex(bool recursive)
+
+ Use the constructor that takes a RecursionMode parameter instead.
+*/
+
+/*!
+ \internal helper for lockInline()
+ */
+void QMutex::lockInternal()
+{
+ QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
+
+ if (QThread::idealThreadCount() == 1) {
+ // don't spin on single cpu machines
+ bool isLocked = d->wait();
+ Q_ASSERT_X(isLocked, "QMutex::lock",
+ "Internal error, infinite wait has timed out.");
+ Q_UNUSED(isLocked);
+ return;
+ }
+
+ QElapsedTimer elapsedTimer;
+ elapsedTimer.start();
+ do {
+ qint64 spinTime = elapsedTimer.nsecsElapsed();
+ if (spinTime > d->maximumSpinTime) {
+ // didn't get the lock, wait for it, since we're not going to gain anything by spinning more
+ elapsedTimer.start();
+ bool isLocked = d->wait();
+ Q_ASSERT_X(isLocked, "QMutex::lock",
+ "Internal error, infinite wait has timed out.");
+ Q_UNUSED(isLocked);
+
+ qint64 maximumSpinTime = d->maximumSpinTime;
+ qint64 averageWaitTime = d->averageWaitTime;
+ qint64 actualWaitTime = elapsedTimer.nsecsElapsed();
+ if (actualWaitTime < (QMutexPrivate::MaximumSpinTimeThreshold * 3 / 2)) {
+ // measure the wait times
+ averageWaitTime = d->averageWaitTime = qMin((averageWaitTime + actualWaitTime) / 2, qint64(QMutexPrivate::MaximumSpinTimeThreshold));
+ }
+
+ // adjust the spin count when spinning does not benefit contention performance
+ if ((spinTime + actualWaitTime) - qint64(QMutexPrivate::MaximumSpinTimeThreshold) >= qint64(QMutexPrivate::MaximumSpinTimeThreshold)) {
+ // long waits, stop spinning
+ d->maximumSpinTime = 0;
+ } else {
+ // allow spinning if wait times decrease, but never spin more than the average wait time (otherwise we may perform worse)
+ d->maximumSpinTime = qBound(qint64(averageWaitTime * 3 / 2), maximumSpinTime / 2, qint64(QMutexPrivate::MaximumSpinTimeThreshold));
+ }
+ return;
+ }
+ // be a good citizen... yielding lets something else run if there is something to run, but may also relieve memory pressure if not
+ QThread::yieldCurrentThread();
+ } while (d->contenders != 0 || !d->contenders.testAndSetAcquire(0, 1));
+
+ // spinning is working, do not change the spin time (unless we are using much less time than allowed to spin)
+ qint64 maximumSpinTime = d->maximumSpinTime;
+ qint64 spinTime = elapsedTimer.nsecsElapsed();
+ if (spinTime < maximumSpinTime / 2) {
+ // we are using much less time than we need, adjust the limit
+ d->maximumSpinTime = qBound(qint64(d->averageWaitTime * 3 / 2), maximumSpinTime / 2, qint64(QMutexPrivate::MaximumSpinTimeThreshold));
+ }
+}
+
+/*!
+ \internal
+*/
+void QMutex::unlockInternal()
+{
+ static_cast<QMutexPrivate *>(d)->wakeUp();
+}
+
+/*!
+ \fn QMutex::lockInline()
+ \internal
+ inline version of QMutex::lock()
+*/
+
+/*!
+ \fn QMutex::unlockInline()
+ \internal
+ inline version of QMutex::unlock()
+*/
+
+/*!
+ \fn QMutex::tryLockInline()
+ \internal
+ inline version of QMutex::tryLock()
+*/
+
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_THREAD