QMutex is now just a pointer

And added a POD QBasicMutex. (QBasicMutex* can safely be
static_cast'ed to QMutex*)

The d pointer is not anymore always a QMutexPrivate.

If d == 0x0: the mutex is unlocked
If d == 0x1: the mutex is locked, uncontended

On linux:
if d == 0x3: the mutex is locked contended, waiting on a futex
If d is a pointer, it is a recursive mutex.

On non-linux platforms:
When a thread tries to lock a mutex for which d == 0x1, it will try to
assing it a QMutexPrivated (allocated from a freelist) in order to wait
for it.

Change-Id: Ie1431cd9402a576fdd9a693cfd747166eebf5622
Reviewed-by: Bradley T. Hughes <bradley.hughes@nokia.com>
Reviewed-on: http://codereview.qt.nokia.com/2116
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Olivier Goffart <olivier.goffart@nokia.com>
This commit is contained in:
Olivier Goffart 2011-07-02 15:13:12 +02:00 committed by Qt by Nokia
parent 487583459e
commit 86a237929e
14 changed files with 685 additions and 462 deletions

View File

@ -878,7 +878,7 @@ QObject::~QObject()
if (c->next) c->next->prev = c->prev; if (c->next) c->next->prev = c->prev;
} }
if (needToUnlock) if (needToUnlock)
m->unlockInline(); m->unlock();
connectionList.first = c->nextConnectionList; connectionList.first = c->nextConnectionList;
delete c; delete c;
@ -902,7 +902,7 @@ QObject::~QObject()
bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m); bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
//the node has maybe been removed while the mutex was unlocked in relock? //the node has maybe been removed while the mutex was unlocked in relock?
if (!node || node->sender != sender) { if (!node || node->sender != sender) {
m->unlockInline(); m->unlock();
continue; continue;
} }
node->receiver = 0; node->receiver = 0;
@ -912,7 +912,7 @@ QObject::~QObject()
node = node->next; node = node->next;
if (needToUnlock) if (needToUnlock)
m->unlockInline(); m->unlock();
} }
} }
@ -3076,7 +3076,7 @@ bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::Connection *c,
} }
if (needToUnlock) if (needToUnlock)
receiverMutex->unlockInline(); receiverMutex->unlock();
c->receiver = 0; c->receiver = 0;

View File

@ -49,8 +49,25 @@
#include "qthread.h" #include "qthread.h"
#include "qmutex_p.h" #include "qmutex_p.h"
#ifndef Q_OS_LINUX
#include "private/qfreelist_p.h"
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
/*!
\class QBasicMutex
\brief QMutex POD
\internal
\ingroup thread
- Can be used as global static object.
- Always non-recursive
- Do not use tryLock with timeout > 0, else you can have a leak (see the ~QMutex destructor)
*/
/*! /*!
\class QMutex \class QMutex
\brief The QMutex class provides access serialization between threads. \brief The QMutex class provides access serialization between threads.
@ -122,8 +139,12 @@ QT_BEGIN_NAMESPACE
\sa lock(), unlock() \sa lock(), unlock()
*/ */
QMutex::QMutex(RecursionMode mode) QMutex::QMutex(RecursionMode mode)
: d(new QMutexPrivate(mode)) {
{ } if (mode == Recursive)
d = new QRecursiveMutexPrivate;
else
d = 0;
}
/*! /*!
Destroys the mutex. Destroys the mutex.
@ -131,9 +152,18 @@ QMutex::QMutex(RecursionMode mode)
\warning Destroying a locked mutex may result in undefined behavior. \warning Destroying a locked mutex may result in undefined behavior.
*/ */
QMutex::~QMutex() QMutex::~QMutex()
{ delete static_cast<QMutexPrivate *>(d); } {
if (isRecursive())
delete static_cast<QRecursiveMutexPrivate *>(d._q_value);
else if (d) {
#ifndef Q_OS_LINUX
if (d->possiblyUnlocked && tryLock()) { unlock(); return; }
#endif
qWarning("QMutex: destroying locked mutex");
}
}
/*! /*! \fn void QMutex::lock()
Locks the mutex. If another thread has locked the mutex then this Locks the mutex. If another thread has locked the mutex then this
call will block until that thread has unlocked it. call will block until that thread has unlocked it.
@ -145,40 +175,8 @@ QMutex::~QMutex()
\sa unlock() \sa unlock()
*/ */
void QMutex::lock()
{
QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
Qt::HANDLE self;
if (d->recursive) { /*!\fn bool QMutex::trylock()
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 Attempts to lock the mutex. If the lock was obtained, this function
returns true. If another thread has locked the mutex, this returns true. If another thread has locked the mutex, this
function returns false immediately. function returns false immediately.
@ -195,36 +193,9 @@ void QMutex::lock()
\sa lock(), unlock() \sa lock(), unlock()
*/ */
bool QMutex::tryLock()
{
QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d);
Qt::HANDLE self;
if (d->recursive) { /*! \fn bool QMutex::tryLock(int timeout)
self = QThread::currentThreadId(); \overload
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 Attempts to lock the mutex. This function returns true if the lock
was obtained; otherwise it returns false. If another thread has was obtained; otherwise it returns false. If another thread has
@ -247,81 +218,30 @@ bool QMutex::tryLock()
\sa lock(), unlock() \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));
}
/*! /*! \fn void QMutex::unlock()
Unlocks the mutex. Attempting to unlock a mutex in a different Unlocks the mutex. Attempting to unlock a mutex in a different
thread to the one that locked it results in an error. Unlocking a thread to the one that locked it results in an error. Unlocking a
mutex that is not locked results in undefined behavior. mutex that is not locked results in undefined behavior.
\sa lock() \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() \fn void QMutex::isRecursive()
\since 5.0
Returns true if the mutex is locked by another thread; otherwise Returns true if the mutex is recursive
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
*/ */
bool QBasicMutex::isRecursive() {
QMutexPrivate *d = this->d;
if (quintptr(d) <= 0x3)
return false;
return d->recursive;
}
/*! /*!
\class QMutexLocker \class QMutexLocker
@ -418,96 +338,217 @@ void QMutex::unlock()
\sa unlock() \sa unlock()
*/ */
#ifndef Q_OS_LINUX //linux implementation is in qmutex_linux.cpp
/*! /*!
\fn QMutex::QMutex(bool recursive) \internal helper for lock()
Use the constructor that takes a RecursionMode parameter instead.
*/
/*!
\internal helper for lockInline()
*/ */
void QMutex::lockInternal() bool QBasicMutex::lockInternal(int timeout)
{ {
QMutexPrivate *d = static_cast<QMutexPrivate *>(this->d); while (!fastTryLock()) {
QMutexPrivate *d = this->d;
if (!d) // if d is 0, the mutex is unlocked
continue;
if (QThread::idealThreadCount() == 1) { if (d == dummyLocked()) {
// don't spin on single cpu machines if (timeout == 0)
bool isLocked = d->wait(); return false;
Q_ASSERT_X(isLocked, "QMutex::lock", QMutexPrivate *newD = QMutexPrivate::allocate();
"Internal error, infinite wait has timed out."); if (!this->d.testAndSetOrdered(d, newD)) {
Q_UNUSED(isLocked); //Either the mutex is already unlocked, or another thread already set it.
return; newD->deref();
} continue;
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));
} }
d = newD;
// adjust the spin count when spinning does not benefit contention performance //the d->refCount is already 1 the deref will occurs when we unlock
if ((spinTime + actualWaitTime) - qint64(QMutexPrivate::MaximumSpinTimeThreshold) >= qint64(QMutexPrivate::MaximumSpinTimeThreshold)) { } else if (d->recursive) {
// long waits, stop spinning return static_cast<QRecursiveMutexPrivate *>(d)->lock(timeout);
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) if (timeout == 0 && !d->possiblyUnlocked)
qint64 maximumSpinTime = d->maximumSpinTime; return false;
qint64 spinTime = elapsedTimer.nsecsElapsed();
if (spinTime < maximumSpinTime / 2) { if (!d->ref())
// we are using much less time than we need, adjust the limit continue; //that QMutexPrivate was already released
d->maximumSpinTime = qBound(qint64(d->averageWaitTime * 3 / 2), maximumSpinTime / 2, qint64(QMutexPrivate::MaximumSpinTimeThreshold));
if (d != this->d) {
//Either the mutex is already unlocked, or relocked with another mutex
d->deref();
continue;
}
int old_waiters;
do {
old_waiters = d->waiters;
if (old_waiters == -QMutexPrivate::BigNumber) {
// we are unlocking, and the thread that unlocks is about to change d to 0
// we try to aquire the mutex by changing to dummyLocked()
if (this->d.testAndSetAcquire(d, dummyLocked())) {
// Mutex aquired
Q_ASSERT(d->waiters == -QMutexPrivate::BigNumber || d->waiters == 0);
d->waiters = 0;
d->deref();
return true;
} else {
Q_ASSERT(d != this->d); //else testAndSetAcquire should have succeeded
// Mutex is likely to bo 0, we should continue the outer-loop,
// set old_waiters to the magic value of BigNumber
old_waiters = QMutexPrivate::BigNumber;
break;
}
}
} while (!d->waiters.testAndSetRelaxed(old_waiters, old_waiters + 1));
if (d != this->d) {
// Mutex was unlocked.
if (old_waiters != QMutexPrivate::BigNumber) {
//we did not break the previous loop
Q_ASSERT(d->waiters >= 1);
d->waiters.deref();
}
d->deref();
continue;
}
if (d->wait(timeout)) {
if (d->possiblyUnlocked && d->possiblyUnlocked.testAndSetRelaxed(true, false))
d->deref();
d->derefWaiters(1);
//we got the lock. (do not deref)
Q_ASSERT(d == this->d);
return true;
} else {
Q_ASSERT(timeout >= 0);
//timeout
d->derefWaiters(1);
//There may be a race in which the mutex is unlocked right after we timed out,
// and before we deref the waiters, so maybe the mutex is actually unlocked.
if (!d->possiblyUnlocked.testAndSetRelaxed(false, true))
d->deref();
return false;
}
} }
Q_ASSERT(this->d);
return true;
} }
/*! /*!
\internal \internal
*/ */
void QMutex::unlockInternal() void QBasicMutex::unlockInternal()
{ {
static_cast<QMutexPrivate *>(d)->wakeUp(); QMutexPrivate *d = this->d;
Q_ASSERT(d); //we must be locked
Q_ASSERT(d != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed
if (d->recursive) {
static_cast<QRecursiveMutexPrivate *>(d)->unlock();
return;
}
if (d->waiters.fetchAndAddRelease(-QMutexPrivate::BigNumber) == 0) {
//there is no one waiting on this mutex anymore, set the mutex as unlocked (d = 0)
if (this->d.testAndSetRelease(d, 0)) {
if (d->possiblyUnlocked && d->possiblyUnlocked.testAndSetRelaxed(true, false))
d->deref();
}
d->derefWaiters(0);
} else {
d->derefWaiters(0);
//there are thread waiting, transfer the lock.
d->wakeUp();
}
d->deref();
}
//The freelist managment
namespace {
struct FreeListConstants : QFreeListDefaultConstants {
enum { BlockCount = 4, MaxIndex=0xffff };
static const int Sizes[BlockCount];
};
const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
16,
128,
1024,
FreeListConstants::MaxIndex - (16-128-1024)
};
typedef QFreeList<QMutexPrivate, FreeListConstants> FreeList;
Q_GLOBAL_STATIC(FreeList, freelist);
}
QMutexPrivate *QMutexPrivate::allocate()
{
int i = freelist()->next();
QMutexPrivate *d = &(*freelist())[i];
d->id = i;
Q_ASSERT(d->refCount == 0);
Q_ASSERT(!d->recursive);
Q_ASSERT(!d->possiblyUnlocked);
Q_ASSERT(d->waiters == 0);
d->refCount = 1;
return d;
}
void QMutexPrivate::release()
{
Q_ASSERT(!recursive);
Q_ASSERT(refCount == 0);
Q_ASSERT(!possiblyUnlocked);
Q_ASSERT(waiters == 0);
freelist()->release(id);
}
// atomically substract "value" to the waiters, and remove the QMutexPrivate::BigNumber flag
void QMutexPrivate::derefWaiters(int value)
{
int old_waiters;
int new_waiters;
do {
old_waiters = waiters;
new_waiters = old_waiters;
if (new_waiters < 0) {
new_waiters += QMutexPrivate::BigNumber;
}
new_waiters -= value;
} while (!waiters.testAndSetRelaxed(old_waiters, new_waiters));
}
#endif
/*!
\internal
*/
bool QRecursiveMutexPrivate::lock(int timeout) {
Qt::HANDLE self = QThread::currentThreadId();
if (owner == self) {
++count;
Q_ASSERT_X(count != 0, "QMutex::lock", "Overflow in recursion counter");
return true;
}
bool success = true;
if (timeout == -1) {
mutex.lock();
} else {
success = mutex.tryLock(timeout);
}
if (success)
owner = self;
return success;
} }
/*! /*!
\fn QMutex::lockInline()
\internal \internal
inline version of QMutex::lock() */
*/ void QRecursiveMutexPrivate::unlock()
{
/*! if (count > 0) {
\fn QMutex::unlockInline() count--;
\internal } else {
inline version of QMutex::unlock() owner = 0;
*/ mutex.unlock();
}
/*! }
\fn QMutex::tryLockInline()
\internal
inline version of QMutex::tryLock()
*/
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -52,47 +52,64 @@ QT_BEGIN_NAMESPACE
QT_MODULE(Core) QT_MODULE(Core)
#ifndef QT_NO_THREAD #if !defined(QT_NO_THREAD) && !defined(qdoc)
class QAtomicInt; class QMutexPrivate;
class QMutexData;
class Q_CORE_EXPORT QMutex class Q_CORE_EXPORT QBasicMutex
{ {
friend class QWaitCondition;
friend class QWaitConditionPrivate;
public: public:
enum RecursionMode { NonRecursive, Recursive }; inline void lock() {
if (!fastTryLock())
lockInternal();
}
explicit QMutex(RecursionMode mode = NonRecursive); inline void unlock() {
~QMutex(); Q_ASSERT(d); //mutex must be locked
if (!d.testAndSetRelease(dummyLocked(), 0))
unlockInternal();
}
void lock(); //### Qt5: make inline; bool tryLock(int timeout = 0) {
inline void lockInline(); return fastTryLock() || lockInternal(timeout);
bool tryLock(); //### Qt5: make inline; }
bool tryLock(int timeout);
inline bool tryLockInline(); bool isRecursive();
void unlock(); //### Qt5: make inline;
inline void unlockInline();
private: private:
void lockInternal(); inline bool fastTryLock() {
return d.testAndSetAcquire(0, dummyLocked());
}
bool lockInternal(int timeout = -1);
void unlockInternal(); void unlockInternal();
Q_DISABLE_COPY(QMutex)
QMutexData *d; QBasicAtomicPointer<QMutexPrivate> d;
static inline QMutexPrivate *dummyLocked() {
return reinterpret_cast<QMutexPrivate *>(quintptr(1));
}
friend class QMutex;
friend class QMutexPrivate;
};
class Q_CORE_EXPORT QMutex : public QBasicMutex {
public:
enum RecursionMode { NonRecursive, Recursive };
explicit QMutex(RecursionMode mode = NonRecursive);
~QMutex();
private:
Q_DISABLE_COPY(QMutex)
}; };
class Q_CORE_EXPORT QMutexLocker class Q_CORE_EXPORT QMutexLocker
{ {
public: public:
inline explicit QMutexLocker(QMutex *m) inline explicit QMutexLocker(QBasicMutex *m)
{ {
Q_ASSERT_X((reinterpret_cast<quintptr>(m) & quintptr(1u)) == quintptr(0), Q_ASSERT_X((reinterpret_cast<quintptr>(m) & quintptr(1u)) == quintptr(0),
"QMutexLocker", "QMutex pointer is misaligned"); "QMutexLocker", "QMutex pointer is misaligned");
if (m) { if (m) {
m->lockInline(); m->lock();
val = reinterpret_cast<quintptr>(m) | quintptr(1u); val = reinterpret_cast<quintptr>(m) | quintptr(1u);
} else { } else {
val = 0; val = 0;
@ -104,7 +121,7 @@ public:
{ {
if ((val & quintptr(1u)) == quintptr(1u)) { if ((val & quintptr(1u)) == quintptr(1u)) {
val &= ~quintptr(1u); val &= ~quintptr(1u);
mutex()->unlockInline(); mutex()->unlock();
} }
} }
@ -112,7 +129,7 @@ public:
{ {
if (val) { if (val) {
if ((val & quintptr(1u)) == quintptr(0u)) { if ((val & quintptr(1u)) == quintptr(0u)) {
mutex()->lockInline(); mutex()->lock();
val |= quintptr(1u); val |= quintptr(1u);
} }
} }
@ -138,54 +155,9 @@ private:
quintptr val; quintptr val;
}; };
class QMutexData
{
public:
QAtomicInt contenders;
const uint recursive : 1;
uint reserved : 31;
protected:
QMutexData(QMutex::RecursionMode mode);
~QMutexData();
};
#ifdef QT_NO_DEBUG
inline void QMutex::unlockInline()
{
if (d->recursive) {
unlock();
} else if (!d->contenders.testAndSetRelease(1, 0)) {
unlockInternal();
}
}
inline bool QMutex::tryLockInline()
{
if (d->recursive) {
return tryLock();
} else {
return d->contenders.testAndSetAcquire(0, 1);
}
}
inline void QMutex::lockInline()
{
if (d->recursive) {
lock();
} else if(!tryLockInline()) {
lockInternal();
}
}
#else // QT_NO_DEBUG
//in debug we do not use inline calls in order to allow debugging tools
// to hook the mutex locking functions.
inline void QMutex::unlockInline() { unlock(); }
inline bool QMutex::tryLockInline() { return tryLock(); }
inline void QMutex::lockInline() { lock(); }
#endif // QT_NO_DEBUG
#else // QT_NO_THREAD #else // QT_NO_THREAD or qdoc
class Q_CORE_EXPORT QMutex class Q_CORE_EXPORT QMutex
@ -194,14 +166,11 @@ public:
enum RecursionMode { NonRecursive, Recursive }; enum RecursionMode { NonRecursive, Recursive };
inline explicit QMutex(RecursionMode mode = NonRecursive) { Q_UNUSED(mode); } inline explicit QMutex(RecursionMode mode = NonRecursive) { Q_UNUSED(mode); }
inline ~QMutex() {}
static inline void lock() {} static inline void lock() {}
static inline void lockInline() {}
static inline bool tryLock(int timeout = 0) { Q_UNUSED(timeout); return true; } static inline bool tryLock(int timeout = 0) { Q_UNUSED(timeout); return true; }
static inline bool tryLockInline() { return true; }
static inline void unlock() {} static inline void unlock() {}
static inline void unlockInline() {} static inline bool isRecursive() { return true; }
private: private:
Q_DISABLE_COPY(QMutex) Q_DISABLE_COPY(QMutex)
@ -221,7 +190,9 @@ private:
Q_DISABLE_COPY(QMutexLocker) Q_DISABLE_COPY(QMutexLocker)
}; };
#endif // QT_NO_THREAD typedef QMutex QBasicMutex;
#endif // QT_NO_THREAD or qdoc
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -0,0 +1,138 @@
/****************************************************************************
**
** 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$
** GNU Lesser General Public License Usage
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qplatformdefs.h"
#include "qmutex.h"
#ifndef QT_NO_THREAD
#include "qatomic.h"
#include "qmutex_p.h"
# include "qelapsedtimer.h"
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <errno.h>
QT_BEGIN_NAMESPACE
static inline int _q_futex(QMutexPrivate *volatile *addr, int op, int val, const struct timespec *timeout)
{
volatile int *int_addr = reinterpret_cast<volatile int *>(addr);
#if Q_BYTE_ORDER == Q_BIG_ENDIAN && QT_POINTER_SIZE == 8
int_addr++; //We want a pointer to the 32 least significant bit of QMutex::d
#endif
int *addr2 = 0;
int val2 = 0;
return syscall(SYS_futex, int_addr, op, val, timeout, addr2, val2);
}
static inline QMutexPrivate *dummyFutexValue()
{
return reinterpret_cast<QMutexPrivate *>(quintptr(3));
}
QMutexPrivate::~QMutexPrivate() {}
QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode)
: recursive(mode == QMutex::Recursive) {}
bool QBasicMutex::lockInternal(int timeout)
{
QElapsedTimer elapsedTimer;
if (timeout >= 1)
elapsedTimer.start();
while (!fastTryLock()) {
QMutexPrivate *d = this->d;
if (!d) // if d is 0, the mutex is unlocked
continue;
if (quintptr(d) <= 0x3) { //d == dummyLocked() || d == dummyFutexValue()
if (timeout == 0)
return false;
while (this->d.fetchAndStoreAcquire(dummyFutexValue()) != 0) {
struct timespec ts, *pts = 0;
if (timeout >= 1) {
// recalculate the timeout
qint64 xtimeout = timeout * 1000 * 1000;
xtimeout -= elapsedTimer.nsecsElapsed();
if (xtimeout <= 0) {
// timer expired after we returned
return false;
}
ts.tv_sec = xtimeout / Q_INT64_C(1000) / 1000 / 1000;
ts.tv_nsec = xtimeout % (Q_INT64_C(1000) * 1000 * 1000);
pts = &ts;
}
int r = _q_futex(&this->d._q_value, FUTEX_WAIT, quintptr(dummyFutexValue()), pts);
if (r != 0 && errno == ETIMEDOUT)
return false;
}
return true;
}
Q_ASSERT(d->recursive);
return static_cast<QRecursiveMutexPrivate *>(d)->lock(timeout);
}
Q_ASSERT(this->d);
return true;
}
void QBasicMutex::unlockInternal()
{
QMutexPrivate *d = this->d;
Q_ASSERT(d); //we must be locked
Q_ASSERT(d != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed
if (d == dummyFutexValue()) {
this->d.fetchAndStoreRelease(0);
_q_futex(&this->d._q_value, FUTEX_WAKE, 1, 0);
return;
}
Q_ASSERT(d->recursive);
static_cast<QRecursiveMutexPrivate *>(d)->unlock();
}
QT_END_NAMESPACE
#endif // QT_NO_THREAD

View File

@ -0,0 +1,96 @@
/****************************************************************************
**
** 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$
** GNU Lesser General Public License Usage
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qplatformdefs.h"
#include "qmutex.h"
#if !defined(QT_NO_THREAD)
#include "qmutex_p.h"
#include <mach/mach.h>
#include <mach/task.h>
#include <errno.h>
QT_BEGIN_NAMESPACE
QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode)
: recursive(mode == QMutex::Recursive)
{
kern_return_t r = semaphore_create(mach_task_self(), &mach_semaphore, SYNC_POLICY_FIFO, 0);
if (r != KERN_SUCCESS)
qWarning("QMutex: failed to create semaphore, error %d", r);
}
QMutexPrivate::~QMutexPrivate()
{
kern_return_t r = semaphore_destroy(mach_task_self(), mach_semaphore);
if (r != KERN_SUCCESS)
qWarning("QMutex: failed to destroy semaphore, error %d", r);
}
bool QMutexPrivate::wait(int timeout)
{
kern_return_t r;
if (timeout < 0) {
do {
r = semaphore_wait(mach_semaphore);
} while (r == KERN_ABORTED);
Q_ASSERT(r == KERN_SUCCESS);
} else {
mach_timespec_t ts;
ts.tv_nsec = ((timeout % 1000) * 1000) * 1000;
ts.tv_sec = (timeout / 1000);
r = semaphore_timedwait(mach_semaphore, ts);
}
return (r == KERN_SUCCESS);
}
void QMutexPrivate::wakeUp()
{
semaphore_signal(mach_semaphore);
}
QT_END_NAMESPACE
#endif //QT_NO_THREAD

View File

@ -57,50 +57,80 @@
#include <QtCore/qglobal.h> #include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h> #include <QtCore/qnamespace.h>
#include <QtCore/qmutex.h> #include <QtCore/qmutex.h>
#include <QtCore/qatomic.h>
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
# include <mach/semaphore.h> # include <mach/semaphore.h>
#endif #endif
#if defined(Q_OS_SYMBIAN)
# include <e32std.h>
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QMutexPrivate : public QMutexData { class QMutexPrivate {
public: public:
QMutexPrivate(QMutex::RecursionMode mode);
~QMutexPrivate(); ~QMutexPrivate();
QMutexPrivate(QMutex::RecursionMode mode = QMutex::NonRecursive);
bool wait(int timeout = -1); bool wait(int timeout = -1);
void wakeUp(); void wakeUp();
// 1ms = 1000000ns #if !defined(Q_OS_LINUX)
enum { MaximumSpinTimeThreshold = 1000000 }; // Conrol the lifetime of the privates
volatile qint64 maximumSpinTime; QAtomicInt refCount;
volatile qint64 averageWaitTime; int id;
Qt::HANDLE owner;
uint count;
bool ref() {
Q_ASSERT(refCount >= 0);
int c;
do {
c = refCount;
if (c == 0)
return false;
} while (!refCount.testAndSetRelaxed(c, c + 1));
Q_ASSERT(refCount >= 0);
return true;
}
void deref() {
Q_ASSERT(refCount >=0);
if (!refCount.deref())
release();
Q_ASSERT(refCount >=0);
}
void release();
static QMutexPrivate *allocate();
QAtomicInt waiters; //number of thread waiting
QAtomicInt possiblyUnlocked; //bool saying that a timed wait timed out
enum { BigNumber = 0x100000 }; //Must be bigger than the possible number of waiters (number of threads)
void derefWaiters(int value);
#endif
// handle recursive mutex
bool recursive;
//platform specific stuff
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
semaphore_t mach_semaphore; semaphore_t mach_semaphore;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) && !defined(Q_OS_SYMBIAN) #elif defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
volatile bool wakeup; bool wakeup;
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_cond_t cond; pthread_cond_t cond;
#elif defined(Q_OS_WIN32) || defined(Q_OS_WINCE) #elif defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
HANDLE event; HANDLE event;
#elif defined(Q_OS_SYMBIAN)
RSemaphore lock;
#endif #endif
}; };
inline QMutexData::QMutexData(QMutex::RecursionMode mode) class QRecursiveMutexPrivate : public QMutexPrivate
: recursive(mode == QMutex::Recursive) {
{} public:
QRecursiveMutexPrivate()
: QMutexPrivate(QMutex::Recursive), owner(0), count(0) {}
Qt::HANDLE owner;
uint count;
QMutex mutex;
inline QMutexData::~QMutexData() {} bool lock(int timeout);
void unlock();
};
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -46,142 +46,35 @@
#ifndef QT_NO_THREAD #ifndef QT_NO_THREAD
#include "qatomic.h" #include "qatomic.h"
#include "qmutex_p.h" #include "qmutex_p.h"
#include <errno.h> #include <errno.h>
#if defined(Q_OS_VXWORKS) && defined(wakeup) #if defined(Q_OS_VXWORKS) && defined(wakeup)
#undef wakeup #undef wakeup
#endif #endif
#if defined(Q_OS_MAC)
# include <mach/mach.h>
# include <mach/task.h>
#elif defined(Q_OS_LINUX)
# include <linux/futex.h>
# include <sys/syscall.h>
# include <unistd.h>
# include <QtCore/qelapsedtimer.h>
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#if !defined(Q_OS_MAC) && !defined(Q_OS_LINUX)
static void report_error(int code, const char *where, const char *what) static void report_error(int code, const char *where, const char *what)
{ {
if (code != 0) if (code != 0)
qWarning("%s: %s failure: %s", where, what, qPrintable(qt_error_string(code))); qWarning("%s: %s failure: %s", where, what, qPrintable(qt_error_string(code)));
} }
#endif
QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode) QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode)
: QMutexData(mode), maximumSpinTime(MaximumSpinTimeThreshold), averageWaitTime(0), owner(0), count(0) : recursive(mode == QMutex::Recursive), wakeup(false)
{ {
#if defined(Q_OS_MAC)
kern_return_t r = semaphore_create(mach_task_self(), &mach_semaphore, SYNC_POLICY_FIFO, 0);
if (r != KERN_SUCCESS)
qWarning("QMutex: failed to create semaphore, error %d", r);
#elif !defined(Q_OS_LINUX)
wakeup = false;
report_error(pthread_mutex_init(&mutex, NULL), "QMutex", "mutex init"); report_error(pthread_mutex_init(&mutex, NULL), "QMutex", "mutex init");
report_error(pthread_cond_init(&cond, NULL), "QMutex", "cv init"); report_error(pthread_cond_init(&cond, NULL), "QMutex", "cv init");
#endif
} }
QMutexPrivate::~QMutexPrivate() QMutexPrivate::~QMutexPrivate()
{ {
#if defined(Q_OS_MAC)
kern_return_t r = semaphore_destroy(mach_task_self(), mach_semaphore);
if (r != KERN_SUCCESS)
qWarning("QMutex: failed to destroy semaphore, error %d", r);
#elif !defined(Q_OS_LINUX)
report_error(pthread_cond_destroy(&cond), "QMutex", "cv destroy"); report_error(pthread_cond_destroy(&cond), "QMutex", "cv destroy");
report_error(pthread_mutex_destroy(&mutex), "QMutex", "mutex destroy"); report_error(pthread_mutex_destroy(&mutex), "QMutex", "mutex destroy");
#endif
}
#if defined(Q_OS_MAC)
bool QMutexPrivate::wait(int timeout)
{
if (contenders.fetchAndAddAcquire(1) == 0) {
// lock acquired without waiting
return true;
}
kern_return_t r;
if (timeout < 0) {
do {
r = semaphore_wait(mach_semaphore);
} while (r == KERN_ABORTED);
if (r != KERN_SUCCESS)
qWarning("QMutex: infinite wait failed, error %d", r);
} else {
mach_timespec_t ts;
ts.tv_nsec = ((timeout % 1000) * 1000) * 1000;
ts.tv_sec = (timeout / 1000);
r = semaphore_timedwait(mach_semaphore, ts);
}
contenders.deref();
return r == KERN_SUCCESS;
}
void QMutexPrivate::wakeUp()
{
semaphore_signal(mach_semaphore);
}
#elif defined(Q_OS_LINUX)
static inline int _q_futex(volatile int *addr, int op, int val, const struct timespec *timeout, int *addr2, int val2)
{
return syscall(SYS_futex, addr, op, val, timeout, addr2, val2);
} }
bool QMutexPrivate::wait(int timeout) bool QMutexPrivate::wait(int timeout)
{ {
struct timespec ts, *pts = 0;
QElapsedTimer timer;
if (timeout >= 0) {
ts.tv_nsec = ((timeout % 1000) * 1000) * 1000;
ts.tv_sec = (timeout / 1000);
pts = &ts;
timer.start();
}
while (contenders.fetchAndStoreAcquire(2) > 0) {
int r = _q_futex(&contenders._q_value, FUTEX_WAIT, 2, pts, 0, 0);
if (r != 0 && errno == ETIMEDOUT)
return false;
if (pts) {
// recalculate the timeout
qint64 xtimeout = timeout * 1000 * 1000;
xtimeout -= timer.nsecsElapsed();
if (xtimeout < 0) {
// timer expired after we returned
return false;
}
ts.tv_sec = xtimeout / Q_INT64_C(1000) / 1000 / 1000;
ts.tv_nsec = xtimeout % (Q_INT64_C(1000) * 1000 * 1000);
}
}
return true;
}
void QMutexPrivate::wakeUp()
{
(void) contenders.fetchAndStoreRelease(0);
(void) _q_futex(&contenders._q_value, FUTEX_WAKE, 1, 0, 0, 0);
}
#else // !Q_OS_MAC && !Q_OS_LINUX
bool QMutexPrivate::wait(int timeout)
{
if (contenders.fetchAndAddAcquire(1) == 0) {
// lock acquired without waiting
return true;
}
report_error(pthread_mutex_lock(&mutex), "QMutex::lock", "mutex lock"); report_error(pthread_mutex_lock(&mutex), "QMutex::lock", "mutex lock");
int errorCode = 0; int errorCode = 0;
while (!wakeup) { while (!wakeup) {
@ -190,12 +83,10 @@ bool QMutexPrivate::wait(int timeout)
} else { } else {
struct timeval tv; struct timeval tv;
gettimeofday(&tv, 0); gettimeofday(&tv, 0);
timespec ti; timespec ti;
ti.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000) * 1000; ti.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000) * 1000;
ti.tv_sec = tv.tv_sec + (timeout / 1000) + (ti.tv_nsec / 1000000000); ti.tv_sec = tv.tv_sec + (timeout / 1000) + (ti.tv_nsec / 1000000000);
ti.tv_nsec %= 1000000000; ti.tv_nsec %= 1000000000;
errorCode = pthread_cond_timedwait(&cond, &mutex, &ti); errorCode = pthread_cond_timedwait(&cond, &mutex, &ti);
} }
if (errorCode) { if (errorCode) {
@ -207,10 +98,10 @@ bool QMutexPrivate::wait(int timeout)
report_error(errorCode, "QMutex::lock()", "cv wait"); report_error(errorCode, "QMutex::lock()", "cv wait");
} }
} }
bool ret = wakeup;
wakeup = false; wakeup = false;
report_error(pthread_mutex_unlock(&mutex), "QMutex::lock", "mutex unlock"); report_error(pthread_mutex_unlock(&mutex), "QMutex::lock", "mutex unlock");
contenders.deref(); return ret;
return errorCode == 0;
} }
void QMutexPrivate::wakeUp() void QMutexPrivate::wakeUp()
@ -221,7 +112,6 @@ void QMutexPrivate::wakeUp()
report_error(pthread_mutex_unlock(&mutex), "QMutex::unlock", "mutex unlock"); report_error(pthread_mutex_unlock(&mutex), "QMutex::unlock", "mutex unlock");
} }
#endif // !Q_OS_MAC && !Q_OS_LINUX
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -48,7 +48,7 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode) QMutexPrivate::QMutexPrivate(QMutex::RecursionMode mode)
: QMutexData(mode), maximumSpinTime(MaximumSpinTimeThreshold), averageWaitTime(0), owner(0), count(0) : recursive(mode)
{ {
event = CreateEvent(0, FALSE, FALSE, 0); event = CreateEvent(0, FALSE, FALSE, 0);
if (!event) if (!event)
@ -60,13 +60,7 @@ QMutexPrivate::~QMutexPrivate()
bool QMutexPrivate::wait(int timeout) bool QMutexPrivate::wait(int timeout)
{ {
if (contenders.fetchAndAddAcquire(1) == 0) { return (WaitForSingleObject(event, timeout < 0 ? INFINITE : timeout) == WAIT_OBJECT_0);
// lock acquired without waiting
return true;
}
bool returnValue = (WaitForSingleObject(event, timeout < 0 ? INFINITE : timeout) == WAIT_OBJECT_0);
contenders.deref();
return returnValue;
} }
void QMutexPrivate::wakeUp() void QMutexPrivate::wakeUp()

View File

@ -79,8 +79,8 @@ public:
void relock() void relock()
{ {
if (!locked) { if (!locked) {
if (mtx1) mtx1->lockInline(); if (mtx1) mtx1->lock();
if (mtx2) mtx2->lockInline(); if (mtx2) mtx2->lock();
locked = true; locked = true;
} }
} }
@ -88,8 +88,8 @@ public:
void unlock() void unlock()
{ {
if (locked) { if (locked) {
if (mtx1) mtx1->unlockInline(); if (mtx1) mtx1->unlock();
if (mtx2) mtx2->unlockInline(); if (mtx2) mtx2->unlock();
locked = false; locked = false;
} }
} }
@ -100,10 +100,10 @@ public:
if (mtx1 == mtx2) if (mtx1 == mtx2)
return false; return false;
if (mtx1 < mtx2) { if (mtx1 < mtx2) {
mtx2->lockInline(); mtx2->lock();
return true; return true;
} }
if (!mtx2->tryLockInline()) { if (!mtx2->tryLock()) {
mtx1->unlock(); mtx1->unlock();
mtx2->lock(); mtx2->lock();
mtx1->lock(); mtx1->lock();

View File

@ -146,7 +146,7 @@ bool QWaitCondition::wait(QMutex *mutex, unsigned long time)
{ {
if (! mutex) if (! mutex)
return false; return false;
if (mutex->d->recursive) { if (mutex->isRecursive()) {
qWarning("QWaitCondition: cannot wait on recursive mutexes"); qWarning("QWaitCondition: cannot wait on recursive mutexes");
return false; return false;
} }

View File

@ -164,7 +164,7 @@ bool QWaitCondition::wait(QMutex *mutex, unsigned long time)
{ {
if (!mutex) if (!mutex)
return false; return false;
if (mutex->d->recursive) { if (mutex->isRecursive()) {
qWarning("QWaitCondition::wait: Cannot wait on recursive mutexes"); qWarning("QWaitCondition::wait: Cannot wait on recursive mutexes");
return false; return false;
} }

View File

@ -24,8 +24,7 @@ SOURCES += thread/qatomic.cpp \
thread/qthread.cpp \ thread/qthread.cpp \
thread/qthreadstorage.cpp thread/qthreadstorage.cpp
unix:!symbian:SOURCES += thread/qmutex_unix.cpp \ unix:!symbian:SOURCES += thread/qthread_unix.cpp \
thread/qthread_unix.cpp \
thread/qwaitcondition_unix.cpp thread/qwaitcondition_unix.cpp
symbian:SOURCES += thread/qmutex_symbian.cpp \ symbian:SOURCES += thread/qmutex_symbian.cpp \
@ -39,3 +38,9 @@ win32:SOURCES += thread/qmutex_win.cpp \
integrity:SOURCES += thread/qmutex_unix.cpp \ integrity:SOURCES += thread/qmutex_unix.cpp \
thread/qthread_unix.cpp \ thread/qthread_unix.cpp \
thread/qwaitcondition_unix.cpp thread/qwaitcondition_unix.cpp
unix: {
macx-* { SOURCES += thread/qmutex_mac.cpp }
else:linux-* { SOURCES += thread/qmutex_linux.cpp }
else { SOURCES += thread/qmutex_unix.cpp }
}

View File

@ -65,6 +65,7 @@ private slots:
void stressTest(); void stressTest();
void tryLockRace(); void tryLockRace();
void qtbug16115_trylock(); void qtbug16115_trylock();
void moreStress();
}; };
static const int iterations = 100; static const int iterations = 100;
@ -83,6 +84,8 @@ QMutex normalMutex, recursiveMutex(QMutex::Recursive);
QSemaphore testsTurn; QSemaphore testsTurn;
QSemaphore threadsTurn; QSemaphore threadsTurn;
enum { waitTime = 100 };
void tst_QMutex::tryLock() void tst_QMutex::tryLock()
{ {
// test non-recursive mutex // test non-recursive mutex
@ -109,18 +112,18 @@ void tst_QMutex::tryLock()
threadsTurn.acquire(); threadsTurn.acquire();
QTime timer; QTime timer;
timer.start(); timer.start();
QVERIFY(!normalMutex.tryLock(1000)); QVERIFY(!normalMutex.tryLock(waitTime));
QVERIFY(timer.elapsed() >= 1000); QVERIFY(timer.elapsed() >= waitTime);
testsTurn.release(); testsTurn.release();
threadsTurn.acquire(); threadsTurn.acquire();
timer.start(); timer.start();
QVERIFY(normalMutex.tryLock(1000)); QVERIFY(normalMutex.tryLock(waitTime));
QVERIFY(timer.elapsed() <= 1000); QVERIFY(timer.elapsed() <= waitTime);
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
timer.start(); timer.start();
QVERIFY(!normalMutex.tryLock(1000)); QVERIFY(!normalMutex.tryLock(waitTime));
QVERIFY(timer.elapsed() >= 1000); QVERIFY(timer.elapsed() >= waitTime);
QVERIFY(lockCount.testAndSetRelaxed(1, 0)); QVERIFY(lockCount.testAndSetRelaxed(1, 0));
normalMutex.unlock(); normalMutex.unlock();
testsTurn.release(); testsTurn.release();
@ -132,7 +135,7 @@ void tst_QMutex::tryLock()
threadsTurn.acquire(); threadsTurn.acquire();
timer.start(); timer.start();
QVERIFY(normalMutex.tryLock(0)); QVERIFY(normalMutex.tryLock(0));
QVERIFY(timer.elapsed() < 1000); QVERIFY(timer.elapsed() < waitTime);
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
QVERIFY(!normalMutex.tryLock(0)); QVERIFY(!normalMutex.tryLock(0));
QVERIFY(lockCount.testAndSetRelaxed(1, 0)); QVERIFY(lockCount.testAndSetRelaxed(1, 0));
@ -158,13 +161,13 @@ void tst_QMutex::tryLock()
normalMutex.unlock(); normalMutex.unlock();
threadsTurn.release(); threadsTurn.release();
// thread can't acquire lock, timeout = 1000 // thread can't acquire lock, timeout = waitTime
testsTurn.acquire(); testsTurn.acquire();
normalMutex.lock(); normalMutex.lock();
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
threadsTurn.release(); threadsTurn.release();
// thread can acquire lock, timeout = 1000 // thread can acquire lock, timeout = waitTime
testsTurn.acquire(); testsTurn.acquire();
QVERIFY(lockCount.testAndSetRelaxed(1, 0)); QVERIFY(lockCount.testAndSetRelaxed(1, 0));
normalMutex.unlock(); normalMutex.unlock();
@ -215,17 +218,17 @@ void tst_QMutex::tryLock()
threadsTurn.acquire(); threadsTurn.acquire();
QTime timer; QTime timer;
timer.start(); timer.start();
QVERIFY(!recursiveMutex.tryLock(1000)); QVERIFY(!recursiveMutex.tryLock(waitTime));
QVERIFY(timer.elapsed() >= 1000); QVERIFY(timer.elapsed() >= waitTime);
QVERIFY(!recursiveMutex.tryLock(0)); QVERIFY(!recursiveMutex.tryLock(0));
testsTurn.release(); testsTurn.release();
threadsTurn.acquire(); threadsTurn.acquire();
timer.start(); timer.start();
QVERIFY(recursiveMutex.tryLock(1000)); QVERIFY(recursiveMutex.tryLock(waitTime));
QVERIFY(timer.elapsed() <= 1000); QVERIFY(timer.elapsed() <= waitTime);
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
QVERIFY(recursiveMutex.tryLock(1000)); QVERIFY(recursiveMutex.tryLock(waitTime));
QVERIFY(lockCount.testAndSetRelaxed(1, 2)); QVERIFY(lockCount.testAndSetRelaxed(1, 2));
QVERIFY(lockCount.testAndSetRelaxed(2, 1)); QVERIFY(lockCount.testAndSetRelaxed(2, 1));
recursiveMutex.unlock(); recursiveMutex.unlock();
@ -241,7 +244,7 @@ void tst_QMutex::tryLock()
threadsTurn.acquire(); threadsTurn.acquire();
timer.start(); timer.start();
QVERIFY(recursiveMutex.tryLock(0)); QVERIFY(recursiveMutex.tryLock(0));
QVERIFY(timer.elapsed() < 1000); QVERIFY(timer.elapsed() < waitTime);
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
QVERIFY(recursiveMutex.tryLock(0)); QVERIFY(recursiveMutex.tryLock(0));
QVERIFY(lockCount.testAndSetRelaxed(1, 2)); QVERIFY(lockCount.testAndSetRelaxed(1, 2));
@ -274,7 +277,7 @@ void tst_QMutex::tryLock()
recursiveMutex.unlock(); recursiveMutex.unlock();
threadsTurn.release(); threadsTurn.release();
// thread can't acquire lock, timeout = 1000 // thread can't acquire lock, timeout = waitTime
testsTurn.acquire(); testsTurn.acquire();
recursiveMutex.lock(); recursiveMutex.lock();
QVERIFY(lockCount.testAndSetRelaxed(0, 1)); QVERIFY(lockCount.testAndSetRelaxed(0, 1));
@ -282,7 +285,7 @@ void tst_QMutex::tryLock()
QVERIFY(lockCount.testAndSetRelaxed(1, 2)); QVERIFY(lockCount.testAndSetRelaxed(1, 2));
threadsTurn.release(); threadsTurn.release();
// thread can acquire lock, timeout = 1000 // thread can acquire lock, timeout = waitTime
testsTurn.acquire(); testsTurn.acquire();
QVERIFY(lockCount.testAndSetRelaxed(2, 1)); QVERIFY(lockCount.testAndSetRelaxed(2, 1));
recursiveMutex.unlock(); recursiveMutex.unlock();
@ -436,7 +439,8 @@ void tst_QMutex::lock_unlock_locked_tryLock()
} }
} }
enum { one_minute = 60 * 1000, threadCount = 10 }; enum { one_minute = 6 * 1000, //not really one minute, but else it is too long.
threadCount = 10 };
class StressTestThread : public QThread class StressTestThread : public QThread
{ {
@ -497,7 +501,7 @@ public:
do { do {
if (mutex.tryLock()) if (mutex.tryLock())
mutex.unlock(); mutex.unlock();
} while (t.elapsed() < 20000); } while (t.elapsed() < one_minute/2);
} }
}; };
QMutex TryLockRaceThread::mutex; QMutex TryLockRaceThread::mutex;
@ -534,7 +538,7 @@ void tst_QMutex::qtbug16115_trylock()
TrylockThread(QMutex &mut) : mut(mut) {} TrylockThread(QMutex &mut) : mut(mut) {}
QMutex &mut; QMutex &mut;
void run() { void run() {
for (int i = 0; i < 1000000; ++i) { for (int i = 0; i < 100000; ++i) {
if (mut.tryLock(0)) { if (mut.tryLock(0)) {
if ((++qtbug16115_trylock_counter) != 1) if ((++qtbug16115_trylock_counter) != 1)
++qtbug16115_failure_count; ++qtbug16115_failure_count;
@ -553,7 +557,7 @@ void tst_QMutex::qtbug16115_trylock()
t2.start(); t2.start();
t3.start(); t3.start();
for (int i = 0; i < 1000000; ++i) { for (int i = 0; i < 100000; ++i) {
mut.lock(); mut.lock();
if ((++qtbug16115_trylock_counter) != 1) if ((++qtbug16115_trylock_counter) != 1)
++qtbug16115_failure_count; ++qtbug16115_failure_count;
@ -567,5 +571,70 @@ void tst_QMutex::qtbug16115_trylock()
QCOMPARE(qtbug16115_failure_count, 0); QCOMPARE(qtbug16115_failure_count, 0);
} }
class MoreStressTestThread : public QThread
{
QTime t;
public:
static QAtomicInt lockCount;
static QAtomicInt sentinel[threadCount];
static QMutex mutex[threadCount];
static QAtomicInt errorCount;
void start()
{
t.start();
QThread::start();
}
void run()
{
quint64 i = 0;
while (t.elapsed() < one_minute) {
i++;
uint nb = (i * 9 + lockCount * 13) % threadCount;
QMutexLocker locker(&mutex[nb]);
if (sentinel[nb]) errorCount.ref();
if (sentinel[nb].fetchAndAddRelaxed(5)) errorCount.ref();
if (!sentinel[nb].testAndSetRelaxed(5, 0)) errorCount.ref();
if (sentinel[nb]) errorCount.ref();
lockCount.ref();
nb = (nb * 17 + i * 5 + lockCount * 3) % threadCount;
if (mutex[nb].tryLock()) {
if (sentinel[nb]) errorCount.ref();
if (sentinel[nb].fetchAndAddRelaxed(16)) errorCount.ref();
if (!sentinel[nb].testAndSetRelaxed(16, 0)) errorCount.ref();
if (sentinel[nb]) errorCount.ref();
lockCount.ref();
mutex[nb].unlock();
}
nb = (nb * 15 + i * 47 + lockCount * 31) % threadCount;
if (mutex[nb].tryLock(2)) {
if (sentinel[nb]) errorCount.ref();
if (sentinel[nb].fetchAndAddRelaxed(53)) errorCount.ref();
if (!sentinel[nb].testAndSetRelaxed(53, 0)) errorCount.ref();
if (sentinel[nb]) errorCount.ref();
lockCount.ref();
mutex[nb].unlock();
}
}
}
};
QMutex MoreStressTestThread::mutex[threadCount];
QAtomicInt MoreStressTestThread::lockCount;
QAtomicInt MoreStressTestThread::sentinel[threadCount];
QAtomicInt MoreStressTestThread::errorCount = 0;
void tst_QMutex::moreStress()
{
MoreStressTestThread threads[threadCount];
for (int i = 0; i < threadCount; ++i)
threads[i].start();
QVERIFY(threads[0].wait(one_minute + 10000));
for (int i = 1; i < threadCount; ++i)
QVERIFY(threads[i].wait(10000));
qDebug("locked %d times", int(MoreStressTestThread::lockCount));
QCOMPARE(int(MoreStressTestThread::errorCount), 0);
}
QTEST_MAIN(tst_QMutex) QTEST_MAIN(tst_QMutex)
#include "tst_qmutex.moc" #include "tst_qmutex.moc"

View File

@ -151,7 +151,6 @@ void tst_QMutex::noThread_data()
QTest::addColumn<int>("t"); QTest::addColumn<int>("t");
QTest::newRow("noLock") << 1; QTest::newRow("noLock") << 1;
QTest::newRow("QMutexInline") << 2;
QTest::newRow("QMutex") << 3; QTest::newRow("QMutex") << 3;
QTest::newRow("QMutexLocker") << 4; QTest::newRow("QMutexLocker") << 4;
} }
@ -172,16 +171,6 @@ void tst_QMutex::noThread()
} }
} }
break; break;
case 2:
QBENCHMARK {
count = 0;
for (int i = 0; i < N; i++) {
mtx.lockInline();
count++;
mtx.unlockInline();
}
}
break;
case 3: case 3:
QBENCHMARK { QBENCHMARK {
count = 0; count = 0;