Thread-safe way to build mutex protection into a C++ class?
c++, multithreading, mutex
Solution
Several things:
You need to initialize `mutex` with `pthread_mutex_init` in the constructor and free it with `pthread_mutex_destroy` in the destructor.
You must make your class non-copyable and non-assignable (or otherwise implement copy constructor and assignment operator correctly; see above).
It's worthwhile making a SBRM helper class for the lock:
class Lock
{
pthread_mutex_t & m;
public:
explicit Lock(pthread_mutex_t & _m) : m(_m) { pthread_mutex_lock(&m); }
~Lock() { pthread_mutex_unlock(&m); }
};
Now you can make a synchronized scope like `{ Lock lk(mutex); /* ... */ }`.
As for Question 2: Concurrent access is serialized by means of locking the mutex. One of the competing threads will sleep on the acquisition of the mutex lock.
Problem
I'm trying to implement a producer/consumer model multithreaded program in C++ for a project I'm working on. The basic idea is that the main thread creates a second thread to watch a serial port for new data, process the data and put the result in a buffer that is periodically polled by the main thread. I've never written multi-threaded programs before. I've been reading lots of tutorials, but they're all in C. I think I've got a handle on the basic concepts, but I'm trying to c++ify it. For the buffer, I want to create a data class with mutex protection built in. This is what I came up with. 1) Am I going about this the wrong way? Is there a smarter way to implement a protected data class? 2) What will happen in the following code if two threads try to call `ProtectedBuffer::add_back()` at the same time? ``` #include <deque> #include "pthread.h" template <class T> class ProtectedBuffer { std::deque<T> buffer; pthread_mutex_t mutex; public: void add_back(T data) { pthread_mutex_lock(&mutex); buffer.push_back(data); pthread_mutex_unlock(&mutex); } void get_front(T &data) { pthread_mutex_lock(&mutex); data = buffer.front(); buffer.pop_front(); pthread_mutex_unlock(&mutex); } }; ``` Edit: Thanks for all the great suggestions. I've tried to implement them below. I also added some error checking so if a thread somehow manages to try to lock the same mutex twice it will fail gracefully. I think. ``` #include "pthread.h" #include <deque> class Lock { pthread_mutex_t &m; bool locked; int error; public: explicit Lock(pthread_mutex_t & _m) : m(_m) { error = pthread_mutex_lock(&m); if (error == 0) { locked = true; } else { locked = false; } } ~Lock() { if (locked) pthread_mutex_unlock(&m); } bool is_locked() { return locked; } }; class TryToLock { pthread_mutex_t &m; bool locked; int error; public: explicit TryToLock(pthread_mutex_t & _m) : m(_m) { error = pthread_mutex_trylock(&m); if (error == 0) { locked = true; } else { locked = false; } } ~TryToLock() { if (locked) pthread_mutex_unlock(&m); } bool is_locked() { return locked; } }; template <class T> class ProtectedBuffer{ pthread_mutex_t mutex; pthread_mutexattr_t mattr; std::deque<T> buffer; bool failbit; ProtectedBuffer(const ProtectedBuffer& x); ProtectedBuffer& operator= (const ProtectedBuffer& x); public: ProtectedBuffer() { pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK); pthread_mutex_init(&mutex, &mattr); failbit = false; } ~ProtectedBuffer() { pthread_mutex_destroy(&mutex); pthread_mutexattr_destroy(&mattr); } void add_back(T &data) { Lock lck(mutex); if (!lck.locked()) { failbit = true; return; } buffer.push_back(data); failbit = false; } void get_front(T &data) { Lock lck(mutex); if (!lck.locked()) { failbit = true; return; } if (buffer.empty()) { failbit = true; return; } data = buffer.front(); buffer.pop_front(); failbit = false; } void try_get_front(T &data) { TryToLock lck(mutex); if (!lck.locked()) { failbit = true; return; } if (buffer.empty()) { failbit = true; return; } data = buffer.front(); buffer.pop_front(); failbit = false; } void try_add_back(T &data) { TryToLock lck(mutex); if (!lck.locked()) { failbit = true; return; } buffer.push_back(data); failbit = false; } }; ```