I am trying to design thread-safe data structure that I cna use as a buffer in my application. Can you please give me comments about this code and what can be improved:
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
template <typename T>
class Buffer
{
public:
void add(T num)
{
while (true)
{
std::unique_lock<std::mutex> locker(mu);
buffer.push_back(num);
locker.unlock();
cond.notify_all();
return;
}
}
T remove()
{
while (true)
{
std::unique_lock<std::mutex> locker(mu);
cond.wait(locker, [this](){return buffer.size() > 0;});
T back = buffer.back();
buffer.pop_back();
locker.unlock();
cond.notify_all();
return back;
}
}
int size()
{
std::unique_lock<std::mutex> locker(mu);
int s = buffer.size();
locker.unlock();
return s;
}
private:
std::mutex mu;
std::condition_variable cond;
std::deque<T> buffer;
};
-
1\$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers . \$\endgroup\$Mathieu Guindon– Mathieu Guindon2017年01月06日 21:58:05 +00:00Commented Jan 6, 2017 at 21:58
2 Answers 2
Problems with add
:
- the while(true) is useless
- you want to
notify_one
, notnotify_all
because you've only added one thing, and so only one thread waiting to remove can remove it. - you should overload for two version which take
const T&
andT&&
(the latter wouldstd::move
). This would be optimal in all cases. (analogous topush_back
in standard containers) - You may also want to add an
emplace
to complement theadd()
versions so you can constructT
in-place at the top of the Buffer. - you can use
lock_guard
instead ofunique_lock
and scope it with{}
s. I'd just say always prefer the lighter weight construct when a heavier weight one isn't strictly needed.
problems with remove
:
- the
while (true)
is useless - the cond var lambda can be
return !buffer.empty();
- the back variable can be
std::move
d - what happens if you destruct an empty
Buffer
while a thread is waiting onremove
? That case isn't handled at all, and it's a problem - there's no reason to
notify_all
inremove
. Who is it notifying and why?
problems with size
:
- use a
lock_guard
unlessunique_lock
is needed - just do:
std::lock_guard<std::mutex> lock{mu}; return buffer.size();
-
\$\begingroup\$ Good point about the emplace (emplace_back?). But I don't agree with letting the lock_guard cover the notify as that means the other thread potentially wakes, sees its still locked goes to sleep again, then is re-awakened by the unlock, wasting a lot of cycles. \$\endgroup\$Surt– Surt2017年01月06日 19:57:52 +00:00Commented Jan 6, 2017 at 19:57
-
\$\begingroup\$ I am thinking the same way as @Surt regarding the locking. Actually it (
unique_lock
) has been taken from C++ documentation: en.cppreference.com/w/cpp/thread/condition_variable \$\endgroup\$sebap123– sebap1232017年01月06日 20:01:19 +00:00Commented Jan 6, 2017 at 20:01 -
\$\begingroup\$ @sebap123 you only need that in
remove
. See updated answer \$\endgroup\$David– David2017年01月06日 20:18:11 +00:00Commented Jan 6, 2017 at 20:18 -
1\$\begingroup\$ @sebap123 one way to handle it would be to return a
std::optional<T>
fromremove
and have an additional mechanism to tell remove to exit and return nullopt \$\endgroup\$David– David2017年01月06日 21:03:50 +00:00Commented Jan 6, 2017 at 21:03 -
1\$\begingroup\$ @David I've read about
std::optional
but unfortunately this is only since C++17, and since I am using up to C++14 I cannot use it. Is there any other solution here? \$\endgroup\$sebap123– sebap1232017年01月06日 21:45:40 +00:00Commented Jan 6, 2017 at 21:45
You should use std::stack
if you want to push and pop from the back, using FILO. If you mean to use FIFO std:deque
is the correct choice as it is optimized for back and front access.
As already mentioned the while
is useless and notify_all
is probably wrong.
The placement of the unlock
is good, so that other threads don't wake, see they are still locked, then when you unlock they are awakened again, wasting lots of cycles.
Using unique_lock is an OK choice as it is needed for the condition_variable
anyway.
-
\$\begingroup\$ You are correct with my FIFO approach. I just forget to change from my previous FILO solution. Thank you for pointing that out. \$\endgroup\$sebap123– sebap1232017年01月06日 19:58:19 +00:00Commented Jan 6, 2017 at 19:58