Comment by gpderetta
3 months ago
Given:
Something someting;
async_mutex mtx;
void my_critical_section(Data&);
1:
await mtx.lock();
my_critical_section(something);
await mtx.unlock();
2:
auto my_locked_critical_section() {
await mtx.lock();
my_critical_section(something);
await mtx.unlock();
}
...
await my_locked_critical_section(something);
3:
auto locked(auto mtx, auto critical_section) {
await mtx.lock();
critical_section();
await mtx.unlock();
}
...
await locked(mtx, [&]{ my_critical_section(something); });
4:
template<class T>
struct synchronized {
async_mutex mtx;
T data;
auto async_visit(auto fn) { locked(mtx, [fn,&data]{ fn(data); }); }
};
synchronized<Something> something;
await something.async_visit([](Something& data) { my_critical_section(something); });
If 1 is a mutex, at which point it stops being a mutex? Note that 4 is my initial example.
it's a mutex iff it's acquiring a resource exclusively.
which you don't need to do for synchronization of coroutines since you can control in which order things are scheduled and whether that's done concurrently or not.
Not if you have multiple schedulers. Case in point: asio.strand or execution::on [1].
And even with one scheduler it makes sense to explicitly mark your critical sections.
Really, at the end of the day the primary purpose of a mutex is serialization of all operations on some data. The blocking behaviour is just a way to implement it.
[1] https://en.cppreference.com/w/cpp/execution/on.html
Mutexes are a problematic pattern that doesn't compose, see the article.