mirror of
https://gitlab.com/niansa/libcrosscoro.git
synced 2025-03-06 20:53:32 +01:00
Add coro::latch example (#40)
This commit is contained in:
parent
03f78e2360
commit
95127e2f6e
8 changed files with 176 additions and 16 deletions
|
@ -31,9 +31,15 @@ for file in $(git diff-index --cached --name-only HEAD); do
|
|||
done
|
||||
|
||||
# Update the README.md example code with the given macros.
|
||||
template_contents=$(cat '.githooks/readme-template.md')
|
||||
# All code examples in markdown should be indeded ' '
|
||||
coro_event_cpp_contents=$(cat 'examples/coro_event.cpp')
|
||||
# template_contents=$(cat '.githooks/readme-template.md')
|
||||
cp .githooks/readme-template.md README.md
|
||||
|
||||
template_contents=$(cat 'README.md')
|
||||
coro_event_cpp_contents=$(cat 'examples/coro_event.cpp')
|
||||
echo "${template_contents/\$\{EXAMPLE_CORO_EVENT_CPP\}/$coro_event_cpp_contents}" > README.md
|
||||
|
||||
template_contents=$(cat 'README.md')
|
||||
coro_latch_cpp_contents=$(cat 'examples/coro_latch.cpp')
|
||||
echo "${template_contents/\$\{EXAMPLE_CORO_LATCH_CPP\}/$coro_latch_cpp_contents}" > README.md
|
||||
|
||||
git add README.md
|
||||
|
|
|
@ -50,7 +50,7 @@ ${EXAMPLE_CORO_EVENT_CPP}
|
|||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./Debug/examples/coro_event
|
||||
$ ./examples/coro_event
|
||||
task 1 is waiting on the event...
|
||||
task 2 is waiting on the event...
|
||||
task 3 is waiting on the event...
|
||||
|
@ -60,6 +60,31 @@ task 2 event triggered, now resuming.
|
|||
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.
|
||||
|
||||
```C++
|
||||
${EXAMPLE_CORO_LATCH_CPP}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./examples/coro_latch
|
||||
latch task is now waiting on all children tasks...
|
||||
work task 1 is working...
|
||||
work task 1 is done, counting down on the latch
|
||||
work task 2 is working...
|
||||
work task 2 is done, counting down on the latch
|
||||
work task 3 is working...
|
||||
work task 3 is done, counting down on the latch
|
||||
work task 4 is working...
|
||||
work task 4 is done, counting down on the latch
|
||||
work task 5 is working...
|
||||
work task 5 is done, counting down on the latch
|
||||
latch task children tasks completed, resuming.
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Requirements
|
||||
|
|
83
README.md
83
README.md
|
@ -52,12 +52,12 @@ int main()
|
|||
{
|
||||
coro::event e;
|
||||
|
||||
// This task will wait until the given event has been set before advancings
|
||||
auto make_wait_task = [](const coro::event& e, int i) -> coro::task<int> {
|
||||
// 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 i;
|
||||
co_return;
|
||||
};
|
||||
|
||||
// This task will trigger the event allowing all waiting tasks to proceed.
|
||||
|
@ -68,7 +68,8 @@ int main()
|
|||
};
|
||||
|
||||
// Synchronously wait until all the tasks are completed, this is intentionally
|
||||
// starting the first 3 wait tasks prior to the final set task.
|
||||
// starting the first 3 wait tasks prior to the final set task so the waiters suspend
|
||||
// their coroutine before being resumed.
|
||||
coro::sync_wait(
|
||||
coro::when_all_awaitable(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e)));
|
||||
}
|
||||
|
@ -76,7 +77,7 @@ int main()
|
|||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./Debug/examples/coro_event
|
||||
$ ./examples/coro_event
|
||||
task 1 is waiting on the event...
|
||||
task 2 is waiting on the event...
|
||||
task 3 is waiting on the event...
|
||||
|
@ -86,6 +87,78 @@ task 2 event triggered, now resuming.
|
|||
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.
|
||||
|
||||
```C++
|
||||
#include <coro/coro.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
// This task will wait until the given latch setters have completed.
|
||||
auto make_latch_task = [](coro::latch& l) -> coro::task<void> {
|
||||
std::cout << "latch task is now waiting on all children tasks...\n";
|
||||
co_await l;
|
||||
std::cout << "latch task children tasks completed, resuming.\n";
|
||||
co_return;
|
||||
};
|
||||
|
||||
// This task does 'work' and counts down on the latch when completed. The final child task to
|
||||
// complete will end up resuming the latch task when the latch's count reaches zero.
|
||||
auto make_worker_task = [](coro::latch& l, int64_t i) -> coro::task<void> {
|
||||
std::cout << "work task " << i << " is working...\n";
|
||||
std::cout << "work task " << i << " is done, counting down on the latch\n";
|
||||
l.count_down();
|
||||
co_return;
|
||||
};
|
||||
|
||||
// It is important to note that the latch task must not 'own' the worker tasks within its
|
||||
// coroutine stack frame because the final worker task thread will execute the latch task upon
|
||||
// setting the latch counter to zero. This means that:
|
||||
// 1) final worker task calls count_down() => 0
|
||||
// 2) resume execution of latch task to its next suspend point or completion, IF completed
|
||||
// then this coroutine's stack frame is destroyed!
|
||||
// 3) final worker task continues exection
|
||||
// If the latch task 'own's the worker task objects then they will destruct prior to step (3)
|
||||
// if the latch task completes on that resume, and it will be attempting to execute an already
|
||||
// destructed coroutine frame.
|
||||
// This example correctly has the latch task and all its waiting tasks on the same scope/frame
|
||||
// to avoid this issue.
|
||||
const int64_t num_tasks{5};
|
||||
coro::latch l{num_tasks};
|
||||
std::vector<coro::task<void>> tasks{};
|
||||
|
||||
// Make the latch task first so it correctly waits for all worker tasks to count down.
|
||||
tasks.emplace_back(make_latch_task(l));
|
||||
for (int64_t i = 1; i <= num_tasks; ++i)
|
||||
{
|
||||
tasks.emplace_back(make_worker_task(l, i));
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete.
|
||||
coro::sync_wait(coro::when_all_awaitable(tasks));
|
||||
}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```bash
|
||||
$ ./examples/coro_latch
|
||||
latch task is now waiting on all children tasks...
|
||||
work task 1 is working...
|
||||
work task 1 is done, counting down on the latch
|
||||
work task 2 is working...
|
||||
work task 2 is done, counting down on the latch
|
||||
work task 3 is working...
|
||||
work task 3 is done, counting down on the latch
|
||||
work task 4 is working...
|
||||
work task 4 is done, counting down on the latch
|
||||
work task 5 is working...
|
||||
work task 5 is done, counting down on the latch
|
||||
latch task children tasks completed, resuming.
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Requirements
|
||||
|
|
|
@ -5,8 +5,13 @@ add_executable(coro_event coro_event.cpp)
|
|||
target_compile_features(coro_event PUBLIC cxx_std_20)
|
||||
target_link_libraries(coro_event PUBLIC libcoro)
|
||||
|
||||
add_executable(coro_latch coro_latch.cpp)
|
||||
target_compile_features(coro_latch PUBLIC cxx_std_20)
|
||||
target_link_libraries(coro_latch 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)
|
||||
elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
|
||||
message(FATAL_ERROR "Clang is currently not supported.")
|
||||
else()
|
||||
|
|
|
@ -5,12 +5,12 @@ int main()
|
|||
{
|
||||
coro::event e;
|
||||
|
||||
// This task will wait until the given event has been set before advancings
|
||||
auto make_wait_task = [](const coro::event& e, int i) -> coro::task<int> {
|
||||
// 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 i;
|
||||
co_return;
|
||||
};
|
||||
|
||||
// This task will trigger the event allowing all waiting tasks to proceed.
|
||||
|
@ -21,7 +21,8 @@ int main()
|
|||
};
|
||||
|
||||
// Synchronously wait until all the tasks are completed, this is intentionally
|
||||
// starting the first 3 wait tasks prior to the final set task.
|
||||
// starting the first 3 wait tasks prior to the final set task so the waiters suspend
|
||||
// their coroutine before being resumed.
|
||||
coro::sync_wait(
|
||||
coro::when_all_awaitable(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e)));
|
||||
}
|
||||
|
|
48
examples/coro_latch.cpp
Normal file
48
examples/coro_latch.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include <coro/coro.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
// This task will wait until the given latch setters have completed.
|
||||
auto make_latch_task = [](coro::latch& l) -> coro::task<void> {
|
||||
std::cout << "latch task is now waiting on all children tasks...\n";
|
||||
co_await l;
|
||||
std::cout << "latch task children tasks completed, resuming.\n";
|
||||
co_return;
|
||||
};
|
||||
|
||||
// This task does 'work' and counts down on the latch when completed. The final child task to
|
||||
// complete will end up resuming the latch task when the latch's count reaches zero.
|
||||
auto make_worker_task = [](coro::latch& l, int64_t i) -> coro::task<void> {
|
||||
std::cout << "work task " << i << " is working...\n";
|
||||
std::cout << "work task " << i << " is done, counting down on the latch\n";
|
||||
l.count_down();
|
||||
co_return;
|
||||
};
|
||||
|
||||
// It is important to note that the latch task must not 'own' the worker tasks within its
|
||||
// coroutine stack frame because the final worker task thread will execute the latch task upon
|
||||
// setting the latch counter to zero. This means that:
|
||||
// 1) final worker task calls count_down() => 0
|
||||
// 2) resume execution of latch task to its next suspend point or completion, IF completed
|
||||
// then this coroutine's stack frame is destroyed!
|
||||
// 3) final worker task continues exection
|
||||
// If the latch task 'own's the worker task objects then they will destruct prior to step (3)
|
||||
// if the latch task completes on that resume, and it will be attempting to execute an already
|
||||
// destructed coroutine frame.
|
||||
// This example correctly has the latch task and all its waiting tasks on the same scope/frame
|
||||
// to avoid this issue.
|
||||
const int64_t num_tasks{5};
|
||||
coro::latch l{num_tasks};
|
||||
std::vector<coro::task<void>> tasks{};
|
||||
|
||||
// Make the latch task first so it correctly waits for all worker tasks to count down.
|
||||
tasks.emplace_back(make_latch_task(l));
|
||||
for (int64_t i = 1; i <= num_tasks; ++i)
|
||||
{
|
||||
tasks.emplace_back(make_worker_task(l, i));
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete.
|
||||
coro::sync_wait(coro::when_all_awaitable(tasks));
|
||||
}
|
|
@ -104,8 +104,7 @@ public:
|
|||
* @return A task that wraps the given functor to be executed on the thread pool.
|
||||
*/
|
||||
template<typename functor, typename... arguments>
|
||||
[[nodiscard]] auto schedule(functor&& f, arguments... args) noexcept
|
||||
-> task<decltype(f(std::forward<arguments>(args)...))>
|
||||
[[nodiscard]] auto schedule(functor&& f, arguments... args) -> task<decltype(f(std::forward<arguments>(args)...))>
|
||||
{
|
||||
auto scheduled = schedule();
|
||||
if (!scheduled.has_value())
|
||||
|
|
|
@ -98,7 +98,10 @@ auto thread_pool::executor(std::stop_token stop_token, std::size_t idx) -> void
|
|||
|
||||
if (op != nullptr && op->m_awaiting_coroutine != nullptr)
|
||||
{
|
||||
op->m_awaiting_coroutine.resume();
|
||||
if (!op->m_awaiting_coroutine.done())
|
||||
{
|
||||
op->m_awaiting_coroutine.resume();
|
||||
}
|
||||
m_size.fetch_sub(1, std::memory_order::relaxed);
|
||||
}
|
||||
else
|
||||
|
|
Loading…
Add table
Reference in a new issue