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