diff -r 000000000000 -r 1918ee327afb src/corelib/thread/qmutex.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/corelib/thread/qmutex.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,512 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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" + +#ifndef QT_NO_THREAD +#include "qatomic.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 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() +{ + 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.fetchAndAddAcquire(1) == 0; + if (!isLocked) { +#ifndef QT_NO_DEBUG + if (d->owner == self) + qWarning("QMutex::lock: Deadlock detected in thread %ld", + long(d->owner)); +#endif + + // didn't get the lock, wait for it + isLocked = d->wait(); + Q_ASSERT_X(isLocked, "QMutex::lock", + "Internal error, infinite wait has timed out."); + + // don't need to wait for the lock anymore + d->contenders.deref(); + } + + d->owner = self; + ++d->count; + Q_ASSERT_X(d->count != 0, "QMutex::lock", "Overflow in recursion counter"); + return; + } + +#ifndef QT_NO_DEBUG + self = QThread::currentThreadId(); +#endif + + bool isLocked = d->contenders == 0 && d->contenders.testAndSetAcquire(0, 1); + if (!isLocked) { + int spinCount = 0; + int lastSpinCount = d->lastSpinCount; + + enum { AdditionalSpins = 20, SpinCountPenalizationDivisor = 4 }; + const int maximumSpinCount = lastSpinCount + AdditionalSpins; + + do { + if (spinCount++ > maximumSpinCount) { + // puts("spinning useless, sleeping"); + isLocked = d->contenders.fetchAndAddAcquire(1) == 0; + if (!isLocked) { +#ifndef QT_NO_DEBUG + if (d->owner == self) + qWarning("QMutex::lock: Deadlock detected in thread %ld", + long(d->owner)); +#endif + + // didn't get the lock, wait for it + isLocked = d->wait(); + Q_ASSERT_X(isLocked, "QMutex::lock", + "Internal error, infinite wait has timed out."); + + // don't need to wait for the lock anymore + d->contenders.deref(); + } + // decrease the lastSpinCount since we didn't actually get the lock by spinning + spinCount = -d->lastSpinCount / SpinCountPenalizationDivisor; + break; + } + + isLocked = d->contenders == 0 && d->contenders.testAndSetAcquire(0, 1); + } while (!isLocked); + + // adjust the last spin lock count + lastSpinCount = d->lastSpinCount; + d->lastSpinCount = spinCount >= 0 + ? qMax(lastSpinCount, spinCount) + : lastSpinCount + spinCount; + } + +#ifndef QT_NO_DEBUG + d->owner = self; +#endif +} + +/*! + 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() +{ + 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 == 0 && 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; + } + +#ifndef QT_NO_DEBUG + self = QThread::currentThreadId(); +#endif + bool isLocked = d->contenders == 0 && 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; + } +#ifndef QT_NO_DEBUG + d->owner = self; +#endif + return isLocked; +} + +/*! \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) +{ + 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.fetchAndAddAcquire(1) == 0; + if (!isLocked) { + // didn't get the lock, wait for it + isLocked = d->wait(timeout); + + // don't need to wait for the lock anymore + d->contenders.deref(); + if (!isLocked) + return false; + } + + d->owner = self; + ++d->count; + Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter"); + return true; + } + +#ifndef QT_NO_DEBUG + self = QThread::currentThreadId(); +#endif + bool isLocked = d->contenders.fetchAndAddAcquire(1) == 0; + if (!isLocked) { + // didn't get the lock, wait for it + isLocked = d->wait(timeout); + + // don't need to wait for the lock anymore + d->contenders.deref(); + if (!isLocked) + return false; + } +#ifndef QT_NO_DEBUG + d->owner = self; +#endif + return true; +} + + +/*! + 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() +{ + Q_ASSERT_X(d->owner == QThread::currentThreadId(), "QMutex::unlock()", + "A mutex must be unlocked in the same thread that locked it."); + + if (d->recursive) { + if (!--d->count) { + d->owner = 0; + if (!d->contenders.testAndSetRelease(1, 0)) + d->wakeUp(); + } + } else { +#ifndef QT_NO_DEBUG + d->owner = 0; +#endif + 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. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_THREAD