3 ms·
Given: Something someting; async_mutex mtx; void my_critical_section(Data&); 1: await mtx.lock(); my_critical_section(something); awa
by gpderetta 11mo 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.
- mgaunard 11mo agoit'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.
- gpderetta 11mo agoNot 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 https://en.cppreference.com/w/cpp/execution/on.html
- mgaunard 11mo agoMutexes are a problematic pattern that doesn't compose, see the article.