jbaldwin / libcoro

C++20 coroutine library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Refactor coro::event to condition variable?

yuzhichang opened this issue · comments

coro::event looks like a contition variable without mutex.

In the following code, each iteration is not under protect of a mutex.
So the receiver may miss an event which sent between step 2 and 1. The receiver will never be awake again in this case.

begin loop, each iteration

    1. wait on an event
    1. when awake, check if an expression be true. Break the loop if yes, otherwise continue.

A condition variable(C, Java, Golang and etc.)'s design:

  • User shall acquire the lock explictly befor wait on a condition variable.
  • a condition variable' wait release the lock implictly before yield.
  • a condition variable' wait acquire the lock implictly before return.
  • User shall acquire the lock explictly before signal a condition variable.

Coroutines don't work like traditional threading concepts since they don't "suspend" into the operating system, they yield/pause in user land. I think the part you might be confused on is ii. when awake, this situation in libcoro doesn't happen unless the coro::event is set to completed. All event awaiters upon awaking are guaranteed to proceed because the event has been set or completed.

How to implement a coroutine-safe blocking queue with coro::event? I'm afraid there's data race risk.

It sounds like you want the coro::ring_buffer, coro::event is more or less one off events to resume execution once a single event is completed, rather than a continuous set of items flowing in. coro::ring_buffer will produce immediately if there is space. If there isn't any space the producing coroutine will suspend and will be automatically woken up by a consumer once space is made available. The same is true on the consumer side but in the reverse, it suspends if the ring buffer is empty otherwise it immediately consumes an item from the buffer. I believe you can make a "safe" queue using these methods in coroutines quite easily.

I don't think one off events are generally safe. Consider the sample code in README.md:

#include <coro/coro.hpp>
#include <iostream>

int main()
{
    coro::event e;

    // These tasks will wait until the given event has been set before advancing.
    auto make_wait_task = [](const coro::event& e, uint64_t i) -> coro::task<void> {
        std::cout << "task " << i << " is waiting on the event...\n";
        co_await e;
        std::cout << "task " << i << " event triggered, now resuming.\n";
        co_return;
    };

    // This task will trigger the event allowing all waiting tasks to proceed.
    auto make_set_task = [](coro::event& e) -> coro::task<void> {
        std::cout << "set task is triggering the event\n";
        e.set();
        co_return;
    };

    // Given more than a single task to synchronously wait on, use when_all() to execute all the
    // tasks concurrently on this thread and then sync_wait() for them all to complete.
    coro::sync_wait(coro::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e)));
}

I don't see whether co_await e is ensured be invoked before e.set().

It doesn't have to be, if you call co_await e and e is already set() then it just continues processing, the thread never suspends the coroutine.

Sounds coro::event behaves just like coro:semaphore?
coro::event e ~= coro::semaphore sem{0}
co_wait e ~= sem.acquire()
e.set() ~= sem.release()

You can have 0 or more waiters on an event. All waiters are woken up when the event is set.
Semaphores only let N users into the critical section at a time.

I see. Thanks!