Reputation: 43
I was given this code on sequential programming and lock programming. I have to analyze the difference, but I’m not sure what some of the code is doing. The code is below, and the parts I don’t understand are also mentioned in the bullets.
Thank you for reading, any help would be appreciated!
<pre><code>
#ifndef LOCKEDBUFFER_H
#define LOCKEDBUFFER_H
#include <memory>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "optional.h"
template <typename T>
class locked_buffer {
public:
// Creates buffer of size n
locked_buffer(int n) :
size_{n},
buf_{new optional<T>[size_]}
{
}
// Remove copy constructor
locked_buffer(const locked_buffer &) = delete;
// Destructor (default generated)
~locked_buffer() = default;
// Gets buffer size
int size() const noexcept {
return size_;
}
// Checks if buffer is empty
bool empty() const noexcept {
std::lock_guard<std::mutex> l{mut_};
return is_empty();
}
// Checks if buffer is full
bool full() const noexcept {
std::lock_guard<std::mutex> l{mut_};
return is_full();
}
// Put an optional value in the queue
void put(const optional<T> & x);
// Get a value from the queue
optional<T> get();
private:
// Next circular position to position p
int next_position(int p) const noexcept {
return p + ((p+1>=size_)?(1-size_):1);
}
// Check if buffer is empty without locking
bool is_empty() const noexcept {
return (next_read_ == next_write_);
}
// Check if buffer is full without locking
bool is_full() const noexcept {
const int next = next_position(next_write_);
return next == next_read_;
}
private:
const int size_;
const std::unique_ptr<optional<T>[]> buf_;
int next_read_ = 0;
int next_write_ = 0;
mutable std::mutex mut_;
std::condition_variable not_full_;
std::condition_variable not_empty_;
};
template <typename T>
void locked_buffer<T>::put(const optional<T> & x)
{
using namespace std;
unique_lock<mutex> l{mut_};
not_full_.wait(l, [this] { return !is_full(); });
buf_[next_write_] = x;
next_write_ = next_position(next_write_);
not_empty_.notify_one();
}
template <typename T>
optional<T> locked_buffer<T>::get()
{
using namespace std;
unique_lock<mutex> l{mut_};
not_empty_.wait(l, [this] { return !is_empty(); });
auto res = buf_[next_read_];
next_read_ = next_position(next_read_);
not_full_.notify_one();
return res;
}
#endif
Upvotes: 1
Views: 75
Reputation: 14119
Let's go through it step by step.
Another StackOverflow answer says that mutable allows a const to be modified but I don’t see what that has to do with this project
This does somehow apply to your class. You have this method.
bool empty() const noexcept {
std::lock_guard<std::mutex> l{mut_};
return is_empty();
}
The lock guard expects a non const object reference as the lock_guard
will change the mutex.
But since your method is const you cannot access any member in a non const way. Here is where mutable comes into play.
What is the effect of removing “mutable” in this program? Does it disable the locks?
If you remove the mutable from your mutex
you will get a compile error in the empty()
and the full()
method. So technically the locks are not disabled but your program is ill formed.
Furthering my lack of understanding of this, why is mutable not required for not_full and not_empty?
Well simply because they are used in non const methods.
So no need to make them mutable. If you would use any of them in a const method and you would like to use a non const method on the condition_variable
then you would need to make them mutable.
Upvotes: 3