#ifndef __Locker_h_ #define __Locker_h_ #include #include "MiscSupport.h" ///////////////////////////////////////////////////////////////////// // Locker // // This class is a convenient wrapper around a mutex. This makes it // easy to write code that always acquires a mutex before accessing // the data, and releases the mutex when you are done with the data. // // This implements a very simple and common style of using a mutex. // You identify a set of data all protected by the same mutex. Any // access to the data must happen while holding the mutex. // Traditionally these rules are only enforced by comments. Now // there is no way to accidentally cheat. (Not 100% true. You could // save a pointer somewhere. But it's easy to do things right and // harder to do them wrong.) // // Step 1: Group your data into a struct. Any data type would work, // but in practice we often have groups of things that should all be // locked together. // // struct UserStatus // { // std::set< UserId > pastUsers; // std::map< UserId, UserData > currentUsers; // }; // // Step 2: Do not directly create a field or variable of type // UserStatus. Instead, use the type Locker< UserStatus >. // // class MasterControl // { // ... // Locker< UserStatus > _allUserInfo; // ... // }; // // Step 3: There are several ways to access the data. They all // start with one of the Locker::lock() functions. That is to say, // calling lock() on your locker gives you access to the data inside. // // Step 3a: Call lock() with no arguments and save the result to a // temporary object. Use { and } to set the scope of the object. // Use the temporary object like a smart pointer to access the data. // (-> to access a single member, or * to access the entire object.) // When the temporary object goes out of scope, we will automatically // release the locks. I.e. the data is unlocked at the exact same // moment when the object we were using the access the data goes away. // // void MasterControl::removeIfPresent(UserId id) // { // auto access = _allUserInfo.lock(); // if (access->currentUsers.erase(id)) // access->pastUsers.insert(id); // } // // Step 3b: Call lock() with no arguments and use the accessor // immediately. Lock still creates the temporary object. If you // don't save that object, it will go away on its own as soon as you // get to the next semicolon. // // void MasterControl::dumpSize() const // { // size_t size = _allUserInfo.lock()->currentUsers.size(); // std::cout<isGood?"Good":"Needs improvement") // < class Locker { private: pthread_mutex_t _mutex; T _value; public: template< class... Args > Locker(Args... args) : _value(args...) { assertFalse(pthread_mutex_init(&_mutex, NULL)); } ~Locker() { assertFalse(pthread_mutex_destroy(&_mutex)); } class Lock { private: Locker *_locker; public: Lock(Locker *locker, bool wait) : _locker(locker) { if (wait) // If it's already locked, wait for the other thread to unlock it. pthread_mutex_lock(&_locker->_mutex); else { // If it's already locked, immediately fail. Use operator bool() // to see if it failed or succeeded. int lockError = pthread_mutex_trylock(&_locker->_mutex); if (lockError) { assert(lockError == EBUSY); _locker = NULL; } } } Lock(Lock &&other) : _locker(other._locker) { other._locker = NULL; } ~Lock() { if (_locker) pthread_mutex_unlock(&_locker->_mutex); } T *operator ->() { return &_locker->_value; } T &operator *() { return _locker->_value; } // This object evaluates to true if the lock is in place and it's safe to // access the data. operator bool() const { return _locker; } bool operator !() const { return !_locker; } Lock(Lock const &) = delete; void operator =(Lock const &) = delete; }; Lock lock() { return Lock(this, true); } Lock tryLock() { return Lock(this, false); } template< class Action > auto lock(Action &action) -> decltype(action(*(T *)NULL)) { auto l = lock(); return action(*l); } Locker(Locker const &) = delete; void operator =(Locker const &) = delete; }; #endif