#lock_guard
lock_guard
#queue
queue
#log_task
log_task
#main
main
#General stuff
General stuff
#Q & A
Q & A
#lock_guard
#queue
#log_task
#main
#General stuff
#Q & A
lock_guard
queue
log_task
main
General stuff
Q & A
The destructor never checks if
mtx == nullptr
, which will cause problems iflock_guard
got default-constructed.The
lock_guard(M&)
constructor cannot benoexcept
, aslock
on a standard library compatible mutex class is allowed to throw.However, it could be made conditionally
noexcept
in caseM::lock
isnoexcept
itselflock_guard(M& m) noexcept(noexcept(std::declval<M>().lock())) : mtx{&m} { mtx->lock(); }`
lock_guard(M&)
should be madeexplicit
in order to prevent unwanted conversions.
The destructor never checks if
mtx == nullptr
, which will cause problems iflock_guard
got default-constructed.The
lock_guard(M&)
constructor cannot benoexcept
, aslock
on a standard library compatible mutex class is allowed to throw.
The destructor never checks if
mtx == nullptr
, which will cause problems iflock_guard
got default-constructed.The
lock_guard(M&)
constructor cannot benoexcept
, aslock
on a standard library compatible mutex class is allowed to throw.However, it could be made conditionally
noexcept
in caseM::lock
isnoexcept
itselflock_guard(M& m) noexcept(noexcept(std::declval<M>().lock())) : mtx{&m} { mtx->lock(); }`
lock_guard(M&)
should be madeexplicit
in order to prevent unwanted conversions.
Memory leak:
push(const T&)
neverdelete
snew_elem
.Actually, why allocate
new_elem
at all? The whole first 4 lines could be simplified to:auto node = new queue_node{ elem, nullptr };
push(const T&=T&)
andpop()
may cause undefined behavior by callingone_elem_mtx.unlock()
if it hasn't been locked before.pop()
doesn't updatetail
in case the last element got removed and lets it dangle instead.This will cause undefined behavior upon the next call to
push(const T&)
.This also means that
one_elem_mtx
will not be locked in calls topush(const T&)
orpop()
, sincehead != tail
while the queue is empty again.Race condition:
one_elem_mtx
needs to be acquired after the lock onpop_mtx
/push_mtx
.Reason: Assume we have a queue with two elements in it, and two threads A and B who both want to execute
pop
. Thread A executes until just afterif(head == tail)
(which of course right now evaluates tofalse
) and then gets interrupted by the OS. Thread B runspop()
to completion in the meantime, leaving the queue at one object. Now, assuming the missingtail
update mentioned above gets added, we have a potential data race on access totail
if another Thread C were to runpush(const T&)
.Thread starvation: Once the race condition is fixed, if
pop()
gets called much more frequently thanpush(const T&)
, threads waiting onpop()
might starve threads trying topush(const T&)
from getting access toone_elem_mtx
. Maybe makepop
a blocking operation (using astd::condition_variable
to notify if new elements got inserted)?For a production ready queue, you might want to think about adding a maximum capacity (so the queue doesn't grow too large if elements get added faster than they get removed).
An overload
push(T&&)
might be nice.
Memory leak:
push(const T&)
neverdelete
snew_elem
.Actually, why allocate
new_elem
at all? The whole first 4 lines could be simplified to:auto node = new queue_node{ elem, nullptr };
push(const T&=)
andpop()
may cause undefined behavior by callingone_elem_mtx.unlock()
if it hasn't been locked before.pop()
doesn't updatetail
in case the last element got removed and lets it dangle instead.This will cause undefined behavior upon the next call to
push(const T&)
.This also means that
one_elem_mtx
will not be locked in calls topush(const T&)
orpop()
, sincehead != tail
while the queue is empty again.Race condition:
one_elem_mtx
needs to be acquired after the lock onpop_mtx
/push_mtx
.Reason: Assume we have a queue with two elements in it, and two threads A and B who both want to execute
pop
. Thread A executes until just afterif(head == tail)
(which of course right now evaluates tofalse
) and then gets interrupted by the OS. Thread B runspop()
to completion in the meantime, leaving the queue at one object. Now, assuming the missingtail
update mentioned above gets added, we have a potential data race on access totail
if another Thread C were to runpush(const T&)
.Thread starvation: Once the race condition is fixed, if
pop()
gets called much more frequently thanpush(const T&)
, threads waiting onpop()
might starve threads trying topush(const T&)
from getting access toone_elem_mtx
. Maybe makepop
a blocking operation (using astd::condition_variable
to notify if new elements got inserted)?For a production ready queue, you might want to think about adding a maximum capacity (so the queue doesn't grow too large if elements get added faster than they get removed).
An overload
push(T&&)
might be nice.
Memory leak:
push(const T&)
neverdelete
snew_elem
.Actually, why allocate
new_elem
at all? The whole first 4 lines could be simplified to:auto node = new queue_node{ elem, nullptr };
push(const T&)
andpop()
may cause undefined behavior by callingone_elem_mtx.unlock()
if it hasn't been locked before.pop()
doesn't updatetail
in case the last element got removed and lets it dangle instead.This will cause undefined behavior upon the next call to
push(const T&)
.This also means that
one_elem_mtx
will not be locked in calls topush(const T&)
orpop()
, sincehead != tail
while the queue is empty again.Race condition:
one_elem_mtx
needs to be acquired after the lock onpop_mtx
/push_mtx
.Reason: Assume we have a queue with two elements in it, and two threads A and B who both want to execute
pop
. Thread A executes until just afterif(head == tail)
(which of course right now evaluates tofalse
) and then gets interrupted by the OS. Thread B runspop()
to completion in the meantime, leaving the queue at one object. Now, assuming the missingtail
update mentioned above gets added, we have a potential data race on access totail
if another Thread C were to runpush(const T&)
.Thread starvation: Once the race condition is fixed, if
pop()
gets called much more frequently thanpush(const T&)
, threads waiting onpop()
might starve threads trying topush(const T&)
from getting access toone_elem_mtx
. Maybe makepop
a blocking operation (using astd::condition_variable
to notify if new elements got inserted)?For a production ready queue, you might want to think about adding a maximum capacity (so the queue doesn't grow too large if elements get added faster than they get removed).
An overload
push(T&&)
might be nice.