Introduction
This C++ baremetal concurrency library provides a low-level API for operations that must be protected in a concurrent environment, without having to know platform details.
#include <conc/concurrency.hpp>
conc::call_in_critical_section(
[] { // do something under a critical section
});
conc::call_in_critical_section(
[] { // or, do something under a critical section
},
[] { // when this predicate returns true
return true; });
Desktop concurrency vs the interrupt model
On a microprocessor, there is only one form of critical section: turning off interrupts globally. Because this typically happens in a single-threaded environment, there is little to no efficiency concern. However as soon as multiple threads are introduced this becomes a problem. Having effectively a single global lock to protect every piece of data that can be modified concurrently, even data that are unrelated, is not a good idea.
int data1;
conc::call_in_critical_section(
[] { // read/write data1
});
// ...
int data2;
conc::call_in_critical_section(
[] { // read/write data2
});
With a global critical section mechanism, two entirely separate pieces of code that touch distinct data may still contend for resources. On a single-threaded microprocessor this is not an issue; in a multithreaded environment, it is.
To get around this issue, critical section calls are by default distinct — and the above code will not cause contention — but can be tagged with template arguments to indicate that they protect the same thing:
int data;
struct data_tag;
conc::call_in_critical_section<data_tag>(
[] { // read/write data
});
// ...
conc::call_in_critical_section<data_tag>(
[] { // read/write data again
});
In this case, without tagging there would be a data race as two different
threads may access data
concurrently.
Re-entrancy
In general, re-locking the same mutex recursively is possible using
std::recursive_mutex
but it is almost universally considered bad design to
have to rely on this. Such calls can always be refactored to protect the data
without re-entrancy into the critical section.
int data;
auto recursive_function() {
conc::call_in_critical_section(
[] { // read/write data
// and then possibly make a call to
recursive_function();
});
}
This code will cause a deadlock (even though call_in_critical_section
is
untagged) and should be refactored so that the access to data
is protected for
as small a time as possible, and no functions with potentially-unknown paths
should be called inside the critical section.