Skip to content

Latest commit

 

History

History
186 lines (140 loc) · 5.96 KB

Uthread.md

File metadata and controls

186 lines (140 loc) · 5.96 KB

Uthead

async_simple supports C++20 stackless coroutine and stackful coroutine, which is based on context switching. Uthread is similar to other stackful coroutine, which implements switching by saving the resgister and stack for current stackful coroutine A and resume the register and stack for resuming stackful coroutine B.

The implmentation of Uthread comes from boost library.

async

It is easy to create uthread just like to create a thread. We could create a stackful coroutine by async interface. async would receive a lambda function as the root function and an executor to specify the context the stackful coroutine would execute in.

  • Create a Uthread to a specifiy executor. The async function would return immediately.
#include <async_simple/uthread/Async.h>
using namespace async_simple;

uthread::async<uthread::Launch::Schedule>(<lambda>, ex);
  • Create a Uthread and execute it in current thread. The async function would return after the created Uthread checked out.
#include <async_simple/uthread/Async.h>
using namespace async_simple;

uthread::async<uthread::Launch::Current>(<lambda>, ex);

async

  • We aslo provide the interface like std::async.
template <class F, class... Args,
          typename R = std::invoke_result_t<F&&, Args&&...>>
inline Future<R> async(Launch policy, Attribute attr, F&& f, Args&&... args) 

Parameters

 policy - bitmask value, where individual bits control the allowed methods of execution
   attr - uthread's attribute
      f - Callable object to call 
args... - parameters to pass to f

uthread::Launch

enum class Launch {
    Schedule,
    Current,
};
  • Sepcifies the launch policy for a uthread executed by the uthread::async function.
Constant Explanation
uthread::Launch::Schedule Create a Uthread to a specifiy executor. The async function would return immediately
uthread::Launch::Current Create a Uthread and execute it in current thread. The async function would return after the created Uthread checked out

uthread::Attribute

struct Attribute {
    Executor* ex;
    size_t stack_size = 0;
};
  • Uthread's attribute, ex is the Executor. stack_size is the size of the stack when uthread is running.

Return value

  • return a Future that will eventually hold the result of that function call

Example

TEST_F(UthreadTest, testAsync_v2) {
    std::atomic<int> running = 1;
    async(
        Launch::Schedule, Attribute{.ex = &_executor, .stack_size = 4096},
        [](int a, int b) { return a + b; }, 1, 2)
        .thenValue([&running](int ans) {
            EXPECT_EQ(ans, 3);
            running--;
        });
    EXPECT_EQ(await(async(
                  Launch::Current, Attribute{&_executor},
                  [](int a, int b) { return a * b; }, 2, 512)),
              1024);
    while (running) {
    }
}

CollectAll

async_simple offers collectAll interface for Uthread just as stackless coroutine Lazy and Future/Promise to execture Uthread concurrently.

In the following example, F is a C++ lambda function, the type of returned value value is std::vector<T>, T is the return type of F. If T is void, collectAll would return void.

  • Specify multiple Uthread to executre concurrently. This requires that there is no data race in the multiple Uthread.
#include <async_simple/uthread/Collect.h>
using namespace async_simple;

std::vector<F> v;
auto res = uthread::collectAll<uthread::Launch::Schedule>(v.begin(), v.end(), ex);
  • Specify multiple Uthread to execute in current thread. This is used when there are data races between the functions.
#include <async_simple/uthread/Collect.h>
using namespace async_simple;

std::vector<F> v;
auto res = uthread::collectAll<uthread::Launch::Current>(v.begin(), v.end(), ex);

latch

The semantic of latch is similar with std::latch. In fact, the design of latch looks at std::latch. latch is used to synchronize multiple Uthread. The Uthread waiting for latch would be suspended until the count is zero by calling downCount().

#include <async_simple/uthread/Latch.h>
using namespace async_simple;
static constexpr std::size_t kCount = 10;

uthread::async<Launch::Schedule>([ex]() {
    uthread::Latch latch(kCount);
    for (auto i = 0; i < kCount; ++i) {
        uthread::async<uthread::Launch::Schedule>([latchPtr = &latch]() {
            latchPtr->downCount();
        }, ex);
    }
    latchPtr->await(ex);
}, ex);

await

Uthread and Lazy are two different coroutines. And we find that they could be used together in practice. See HybridCoro. Here we shows how Uthread calls Lazy by await interface.

  • Call non-member functions/lambda with return type Lazy<T>.
#include <async_simple/uthread/Await.h>
using namespace async_simple;

coro::Lazy<T> foo(Ts&&...) { ... }
uthread::await(ex, foo, Ts&&...);

auto lambda = [](Ts&&...) -> coro::Lazy<T> { ... };
uthread::await(ex, lambda, Ts&&...);
  • Call member function with return type Lazy<T>.
#include <async_simple/uthread/Await.h>
using namespace async_simple;

class Foo {
public:
    coro::Lazy<T> bar(Ts&&...) { ... }
};

Foo f;
uthread::await(ex, &Foo::bar, &f, Ts&&...);
  • Await futures Future<T>. Although we implemented Future<T>::wait, it wouldn't call stackful switch in/out. We need to call uthread::await to trigger stackful switching.
#include <async_simple/uthread/Await.h>
using namespace async_simple;

Promise<int> p;
// ...
uthread::await(p.getFuture());

Sanitizer

Newer Compiler-rt will enable Use-After-Return by default. But it can't take care of Uthread well, so when we use Uthread with newer compiler-rt, we need to disable Use-After-Return explicitly by -fsanitize-address-use-after-return=never.