Click here to Skip to main content
13,250,029 members (66,148 online)
Click here to Skip to main content
Add your own
alternative version

Stats

9.9K views
31 bookmarked
Posted 11 May 2017

tlock<> : Any C++ object read/write thread-safe provider

, 29 Aug 2017
Rate this:
Please Sign up or sign in to vote.
An easy locking class

Introduction

Some times you need an object to be accessible from many threads, either read only (so all threads can access), or write (so only one thread can access).

The implementation is based on: 

Using the code

You need a proxy class so, when an object's method is called, the lock/unlock mechanism will be compiled automatically:

class proxy 
    {
    private:
        T *const p;
        RWMUTEX* m;
        int me;
    public:
        proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me) { if (me == 2) m->LockWrite(); else m->LockRead(); }
        ~proxy() { if (me == 2) m->ReleaseWrite(); else  m->ReleaseRead(); }
        T* operator -> () { return p; }
        const T* operator -> () const { return p; }
        T* getp() { return p; }
        const T* getpc() const { return p; }
    };

The constructor and destructor of this class do all the work. They lock with RWMutex before the object method is to be called, and they unlock after the method has been called.

The tlock class will then look like this:

template <typename T> class tlock
        {
        private:
            mutable T t;
            mutable RWMUTEX m;

            class proxy 
                {
                T *const p;
                RWMUTEX* m;
                int me;
                public:
                    proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me) { if (me == 2) m->LockWrite(); else m->LockRead(); }
                    ~proxy() { if (me == 2) m->ReleaseWrite(); else  m->ReleaseRead(); }
                    T* operator -> () { return p; }
                    const T* operator -> () const { return p; }
                    T* getp() { return p; }
                    const T* getpc() const { return p; }
            };

        public:
            template< typename ...Args>
            tlock(Args ... args) : t(args...) {}
            const proxy r() const
                {
                return proxy(&t,&m,1);
                }
            proxy w()
                {
                return proxy(&t, &m, 2);
                }

            void readlock(std::function<void(const T&)> f) const
            {
                auto ww = r();
                f(*ww.getpc());
            }
            void writelock(std::function<void(T&)> f) 
            {
                auto ww = w();
                f(*ww.getp());
            }

            proxy operator -> () { return w(); }
            const proxy operator -> () const { return r(); }

        };
        
    };

 

The r() method is called when you want read-only access to the object. This is the default when operator -> is called on a const object.

The w() method is called when you want write access to the object. This is the default for operator -> if the object is not constant.

The readlock() method is called when you want many operations in a locked read-only object, so it calls your function, passing a reference to the constant, locked object.

The writelock() method is called when you want many operations in a locked read-write object, so it calls your function, passing a reference to the locked object.

Let's see some incorrect usage (without tlock):

vector<int> s;
std::thread t1([&]() { s.push_back(0); });
std::thread t2([&]() { s.push_back(1); });
std::thread t3([&]() { s.push_back(2); });
std::thread t4([&]() { s.push_back(3); });
std::thread t5([&]() { s.push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();

Boom.  

And now, the correct usage:

tlock<vector<int>> s;
std::thread t1([&]() { s->push_back(0); });
std::thread t2([&]() { s->push_back(1); });
std::thread t3([&]() { s->push_back(2); });
std::thread t4([&]() { s->push_back(3); });
std::thread t5([&]() { s->push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();

Now the writing is thread safe.

Using writelock() would be like that:

s.writelock([&](vector<int>& ss)
    {
    ss.push_back(100);
    ss.push_back(150); 
    ss.erase(ss.begin());
    // Safe operations, s is locked while in this function.
    })

 

History

30 - 08 - 2017: Added readlock and writelock convenience functions.
12 - 05 - 2017: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Chourdakis
Engineer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS and Android.

I 've a PhD in Digital Signal Processing and I specialize in Pro Audio applications.

My home page: http://www.michaelchourdakis.com

You may also be interested in...

Pro

Comments and Discussions

 
Questionquestion Pin
Member 1281626726-Jul-17 16:21
memberMember 1281626726-Jul-17 16:21 
AnswerRe: question Pin
Michael Chourdakis10-Aug-17 4:49
memberMichael Chourdakis10-Aug-17 4:49 
Hm? There is no destructor.
QuestionDiff Pin
_kb_24-May-17 20:55
member_kb_24-May-17 20:55 
AnswerRe: Diff Pin
Michael Chourdakis26-May-17 11:54
memberMichael Chourdakis26-May-17 11:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171114.1 | Last Updated 30 Aug 2017
Article Copyright 2017 by Michael Chourdakis
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid