RenderCore/Utilities/CKMutex.h (81 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <RenderCore/CKDefines.h>
#if CK_NOT_SWIFT
#import <stdlib.h>
#import <pthread.h>
#import <RenderCore/RCAssert.h>
#if defined (__GNUC__)
# define CK_NOTHROW __attribute__ ((nothrow))
#else
# define CK_NOTHROW
#endif
/**
For use with CK::StaticMutex only.
*/
#define CK_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER}
#define CK_MUTEX_RECURSIVE_INITIALIZER {PTHREAD_RECURSIVE_MUTEX_INITIALIZER}
// This MUST always execute, even when assertions are disabled. Otherwise all lock operations become no-ops!
// (To be explicit, do not turn this into an RCAssert, NSAssert, assert(), or any other kind of statement where the
// evaluation of x_ can be compiled out.)
#define CK_THREAD_ASSERT_ON_ERROR(x_) do { \
_Pragma("clang diagnostic push"); \
_Pragma("clang diagnostic ignored \"-Wunused-variable\""); \
volatile int res = (x_); \
RCCAssert(res == 0, @" %@ returned %d", @#x_, res); \
_Pragma("clang diagnostic pop"); \
} while (0)
namespace CK {
template<class T>
class Locker
{
T &_l;
public:
Locker (T &l) CK_NOTHROW : _l (l) {
_l.lock ();
}
~Locker () {
_l.unlock ();
}
// non-copyable.
Locker(const Locker<T>&) = delete;
Locker &operator=(const Locker<T>&) = delete;
};
struct Mutex
{
/// Constructs a non-recursive mutex (the default).
Mutex () : Mutex (false) {}
~Mutex () {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_destroy (&_m));
}
Mutex (const Mutex&) = delete;
Mutex &operator=(const Mutex&) = delete;
void lock () noexcept {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex()));
}
void unlock () noexcept {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex()));
}
pthread_mutex_t *mutex () noexcept { return &_m; }
protected:
explicit Mutex (bool recursive) {
if (!recursive) {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, NULL));
} else {
pthread_mutexattr_t attr;
CK_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
CK_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, &attr));
CK_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
}
}
private:
pthread_mutex_t _m;
};
typedef Locker<Mutex> MutexLocker;
/**
If you are creating a static mutex, use StaticMutex and specify its default value as one of CK_MUTEX_INITIALIZER or
CK_MUTEX_RECURSIVE_INITIALIZER. This avoids expensive constructor overhead at startup (or worse, ordering issues
between different static objects). It also avoids running a destructor on app exit time (needless expense).
Note that you can, but should not, use StaticMutex for non-static objects. It will leak its mutex on destruction,
so avoid that!
If you fail to specify a default value (like CK_MUTEX_INITIALIZER) an assert will be thrown when you attempt to lock.
*/
struct StaticMutex
{
pthread_mutex_t _m; // public so it can be provided by CK_MUTEX_INITIALIZER and friends
void lock () noexcept {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex()));
}
void unlock () noexcept {
CK_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex()));
}
pthread_mutex_t *mutex () noexcept { return &_m; }
StaticMutex(const StaticMutex&) = delete;
StaticMutex &operator=(const StaticMutex&) = delete;
};
typedef Locker<StaticMutex> StaticMutexLocker;
} // namespace CK
#endif