mirror of
https://gitlab.com/niansa/libcrosscoro.git
synced 2025-03-06 20:53:32 +01:00
Add coro::generator<T> example (#46)
This commit is contained in:
parent
60994334fe
commit
cb335b4474
7 changed files with 128 additions and 28 deletions
|
@ -46,4 +46,8 @@ template_contents=$(cat 'README.md')
|
|||
example_contents=$(cat 'examples/coro_mutex.cpp')
|
||||
echo "${template_contents/\$\{EXAMPLE_CORO_MUTEX_CPP\}/$example_contents}" > README.md
|
||||
|
||||
template_contents=$(cat 'README.md')
|
||||
example_contents=$(cat 'examples/coro_generator.cpp')
|
||||
echo "${template_contents/\$\{EXAMPLE_CORO_GENERATOR_CPP\}/$example_contents}" > README.md
|
||||
|
||||
git add README.md
|
||||
|
|
|
@ -33,17 +33,24 @@
|
|||
- coro::net::udp_peer
|
||||
|
||||
### A note on co_await
|
||||
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the
|
||||
potential to switch the thread that is executing the currently running coroutine. In general this shouldn't
|
||||
affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local`
|
||||
should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and
|
||||
work stealing on thread pools.
|
||||
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the potential to switch the thread that is executing the currently running coroutine. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on thread pools.
|
||||
|
||||
|
||||
### coro::generator<T>
|
||||
The `coro::generator<T>` construct is a coroutine which can generate one or more values.
|
||||
|
||||
```C++
|
||||
${EXAMPLE_CORO_GENERATOR_CPP}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./examples/coro_generator
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
|
||||
```
|
||||
|
||||
### coro::event
|
||||
The `coro::event` is a thread safe async tool to have 1 or more waiters suspend for an event to be set
|
||||
before proceeding. The implementation of event currently will resume execution of all waiters on the
|
||||
thread that sets the event. If the event is already set when a waiter goes to wait on the thread they
|
||||
will simply continue executing with no suspend or wait time incurred.
|
||||
The `coro::event` is a thread safe async tool to have 1 or more waiters suspend for an event to be set before proceeding. The implementation of event currently will resume execution of all waiters on the thread that sets the event. If the event is already set when a waiter goes to wait on the thread they will simply continue executing with no suspend or wait time incurred.
|
||||
|
||||
```C++
|
||||
${EXAMPLE_CORO_EVENT_CPP}
|
||||
|
@ -62,8 +69,7 @@ task 1 event triggered, now resuming.
|
|||
```
|
||||
|
||||
### coro::latch
|
||||
The `coro::latch` is a thread safe async tool to have 1 waiter suspend until all outstanding events
|
||||
have completed before proceeding.
|
||||
The `coro::latch` is a thread safe async tool to have 1 waiter suspend until all outstanding events have completed before proceeding.
|
||||
|
||||
```C++
|
||||
${EXAMPLE_CORO_LATCH_CPP}
|
||||
|
@ -87,6 +93,9 @@ latch task dependency tasks completed, resuming.
|
|||
```
|
||||
|
||||
### coro::mutex
|
||||
The `coro::mutex` is a thread safe async tool to protect critical sections and only allow a single thread to execute the critical section at any given time. Mutexes that are uncontended are a simple CAS operation with a memory fence 'acquire' to behave similar to `std::mutex`. If the lock is contended then the thread will add itself to a FIFO queue of waiters and yield excution to allow another coroutine to process on that thread while it waits to acquire the lock.
|
||||
|
||||
Its important to note that upon releasing the mutex that thread will immediately start processing the next waiter in line for the `coro::mutex`, the mutex is only unlocked/released once all waiters have been processed. This guarantees fair execution in a FIFO manner, but it also means all coroutines that stack in the waiter queue will end up shifting to the single thread that is executing all waiting coroutines. It is possible to reschedule after the critical section onto a thread pool to re-distribute the work. Perhaps an auto-reschedule on a given thread pool is a good feature to implement in the future to prevent this behavior so the post critical section work in the coroutines is redistributed amongst all available thread pool threads.
|
||||
|
||||
```C++
|
||||
${EXAMPLE_CORO_MUTEX_CPP}
|
||||
|
|
61
README.md
61
README.md
|
@ -33,17 +33,54 @@
|
|||
- coro::net::udp_peer
|
||||
|
||||
### A note on co_await
|
||||
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the
|
||||
potential to switch the thread that is executing the currently running coroutine. In general this shouldn't
|
||||
affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local`
|
||||
should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and
|
||||
work stealing on thread pools.
|
||||
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the potential to switch the thread that is executing the currently running coroutine. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on thread pools.
|
||||
|
||||
|
||||
### coro::generator<T>
|
||||
The `coro::generator<T>` construct is a coroutine which can generate one or more values.
|
||||
|
||||
```C++
|
||||
#include <coro/coro.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
auto task = [](uint64_t count_to) -> coro::task<void> {
|
||||
// Create a generator function that will yield and incrementing
|
||||
// number each time its called.
|
||||
auto gen = []() -> coro::generator<uint64_t> {
|
||||
uint64_t i = 0;
|
||||
while (true)
|
||||
{
|
||||
co_yield i++;
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the next number until its greater than count to.
|
||||
for (auto val : gen())
|
||||
{
|
||||
std::cout << val << ", ";
|
||||
|
||||
if (val >= count_to)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
|
||||
coro::sync_wait(task(100));
|
||||
}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./examples/coro_generator
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
|
||||
```
|
||||
|
||||
### coro::event
|
||||
The `coro::event` is a thread safe async tool to have 1 or more waiters suspend for an event to be set
|
||||
before proceeding. The implementation of event currently will resume execution of all waiters on the
|
||||
thread that sets the event. If the event is already set when a waiter goes to wait on the thread they
|
||||
will simply continue executing with no suspend or wait time incurred.
|
||||
The `coro::event` is a thread safe async tool to have 1 or more waiters suspend for an event to be set before proceeding. The implementation of event currently will resume execution of all waiters on the thread that sets the event. If the event is already set when a waiter goes to wait on the thread they will simply continue executing with no suspend or wait time incurred.
|
||||
|
||||
```C++
|
||||
#include <coro/coro.hpp>
|
||||
|
@ -87,8 +124,7 @@ task 1 event triggered, now resuming.
|
|||
```
|
||||
|
||||
### coro::latch
|
||||
The `coro::latch` is a thread safe async tool to have 1 waiter suspend until all outstanding events
|
||||
have completed before proceeding.
|
||||
The `coro::latch` is a thread safe async tool to have 1 waiter suspend until all outstanding events have completed before proceeding.
|
||||
|
||||
```C++
|
||||
#include <coro/coro.hpp>
|
||||
|
@ -165,6 +201,9 @@ latch task dependency tasks completed, resuming.
|
|||
```
|
||||
|
||||
### coro::mutex
|
||||
The `coro::mutex` is a thread safe async tool to protect critical sections and only allow a single thread to execute the critical section at any given time. Mutexes that are uncontended are a simple CAS operation with a memory fence 'acquire' to behave similar to `std::mutex`. If the lock is contended then the thread will add itself to a FIFO queue of waiters and yield excution to allow another coroutine to process on that thread while it waits to acquire the lock.
|
||||
|
||||
Its important to note that upon releasing the mutex that thread will immediately start processing the next waiter in line for the `coro::mutex`, the mutex is only unlocked/released once all waiters have been processed. This guarantees fair execution in a FIFO manner, but it also means all coroutines that stack in the waiter queue will end up shifting to the single thread that is executing all waiting coroutines. It is possible to reschedule after the critical section onto a thread pool to re-distribute the work. Perhaps an auto-reschedule on a given thread pool is a good feature to implement in the future to prevent this behavior so the post critical section work in the coroutines is redistributed amongst all available thread pool threads.
|
||||
|
||||
```C++
|
||||
#include <coro/coro.hpp>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(libcoro_examples)
|
||||
|
||||
add_executable(coro_generator coro_generator.cpp)
|
||||
target_compile_features(coro_generator PUBLIC cxx_std_20)
|
||||
target_link_libraries(coro_generator PUBLIC libcoro)
|
||||
|
||||
add_executable(coro_event coro_event.cpp)
|
||||
target_compile_features(coro_event PUBLIC cxx_std_20)
|
||||
target_link_libraries(coro_event PUBLIC libcoro)
|
||||
|
@ -14,9 +18,10 @@ target_compile_features(coro_mutex PUBLIC cxx_std_20)
|
|||
target_link_libraries(coro_mutex PUBLIC libcoro)
|
||||
|
||||
if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
|
||||
target_compile_options(coro_event PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_latch PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_mutex PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_generator PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_event PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_latch PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
target_compile_options(coro_mutex PUBLIC -fcoroutines -Wall -Wextra -pipe)
|
||||
elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
|
||||
message(FATAL_ERROR "Clang is currently not supported.")
|
||||
else()
|
||||
|
|
31
examples/coro_generator.cpp
Normal file
31
examples/coro_generator.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include <coro/coro.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
auto task = [](uint64_t count_to) -> coro::task<void> {
|
||||
// Create a generator function that will yield and incrementing
|
||||
// number each time its called.
|
||||
auto gen = []() -> coro::generator<uint64_t> {
|
||||
uint64_t i = 0;
|
||||
while (true)
|
||||
{
|
||||
co_yield i++;
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the next number until its greater than count to.
|
||||
for (auto val : gen())
|
||||
{
|
||||
std::cout << val << ", ";
|
||||
|
||||
if (val >= count_to)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
|
||||
coro::sync_wait(task(100));
|
||||
}
|
|
@ -63,7 +63,7 @@ public:
|
|||
{
|
||||
explicit lock_operation(mutex& m) : m_mutex(m) {}
|
||||
|
||||
auto await_ready() const noexcept -> bool { return m_mutex.try_lock(); }
|
||||
auto await_ready() const noexcept -> bool;
|
||||
auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool;
|
||||
auto await_resume() noexcept -> scoped_lock { return scoped_lock{m_mutex}; }
|
||||
|
||||
|
|
|
@ -19,6 +19,17 @@ auto scoped_lock::unlock() -> void
|
|||
}
|
||||
}
|
||||
|
||||
auto mutex::lock_operation::await_ready() const noexcept -> bool
|
||||
{
|
||||
if (m_mutex.try_lock())
|
||||
{
|
||||
// Since there is no mutex acquired, insert a memory fence to act like it.
|
||||
std::atomic_thread_fence(std::memory_order::acquire);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto mutex::lock_operation::await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool
|
||||
{
|
||||
std::scoped_lock lk{m_mutex.m_waiter_mutex};
|
||||
|
@ -74,8 +85,9 @@ auto mutex::unlock() -> void
|
|||
{
|
||||
// If there were no waiters, release the lock. This is done under the waiter list being
|
||||
// locked so another thread doesn't add themselves to the waiter list before the lock
|
||||
// is actually released.
|
||||
m_state.exchange(false, std::memory_order::release);
|
||||
// is actually released. We can safely used relaxed here since m_waiter_mutex will
|
||||
// perform the release memory order upon unlocking.
|
||||
m_state.exchange(false, std::memory_order::relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue