https://github.com/fuyutsubaki/coffin-goroutine
single header library for supporting goroutine-like concurrency in C++
https://github.com/fuyutsubaki/coffin-goroutine
c-plus-plus concurrency cpp20 header-only single-header-lib
Last synced: 10 months ago
JSON representation
single header library for supporting goroutine-like concurrency in C++
- Host: GitHub
- URL: https://github.com/fuyutsubaki/coffin-goroutine
- Owner: Fuyutsubaki
- License: bsl-1.0
- Created: 2020-09-12T17:50:03.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-02-26T17:53:09.000Z (over 3 years ago)
- Last Synced: 2025-04-18T16:23:42.480Z (about 1 year ago)
- Topics: c-plus-plus, concurrency, cpp20, header-only, single-header-lib
- Language: C++
- Homepage:
- Size: 53.7 KB
- Stars: 3
- Watchers: 1
- Forks: 1
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# coffin/goroutine
"coffin/goroutine" は goroutine/channelのような非同期実行をサポートする シングルヘッダライブラリである
## チュートリアル
チュートリアルで説明するすべてのコードは、下のリンクからブラウザで試すことができる
https://wandbox.org/permlink/3LBaAqOACoKCTmB3
### 準備
#### 1. Scheduler
もしあなたのアプリケーションがSchedulerを持っていない場合、Schedulerを用意する必要がある
- coffin/goroutineはSchedulerを含んでいない。これはアプリケーションにすでにSchedulerが存在する場合、それと共存するためである
```C++
// Scheduler
struct MyScheduler {
boost::asio::io_service io_service_;
std::shared_ptr work_;
void post(std::function && f){
io_service_.post(std::move(f));
}
void run(std::size_t n){
work_ = std::make_shared(boost::asio::io_service::work(io_service_));
std::vector thread_list;
for (std::size_t i = 0; i < n; ++i) {
thread_list.push_back(std::thread{[&]{io_service_.run();}});
}
for(auto&th:thread_list)
th.join();
io_service_.reset();
}
void stop(){
work_.reset();
}
};
static inline MyScheduler global_scheduler;
```
#### 2. ChannelStrategy
1のSchedulerとChannnelをつなぐ、 `ChannelStrategy` を定義する必要がある
するべきことは、渡された goroutineを一度だけ実行する処理をScheduler に postする関数`post_goroutine` を定義することである
```C++
struct MyStrategy{
void post_goroutine(std::shared_ptr && g){
global_scheduler.post([=]()mutable{g->execute();});
}
};
```
### Taskと Goroutine
- TaskとGoroutineを用いることで、Schedulerにtaskをpostすることができる
- GoroutineはTaskから変換することができる
```C++
void spown_task(cfn::Task<> && task){
global_scheduler.post([g = std::make_shared(std::move(task))]{
g->execute();
});
}
cfn::Task<> example1_1(){
std::cout<<1<cfn::Task<>{
std::cout<<2< fibonacci_n(int n, std::shared_ptr> ch){
int x=0;
int y=1;
for(int i=0;isend(x);
int next = x+y;
x=y;
y=next;
}
}
cfn::Task<> example2(){
auto [sender,recver] = cfn::makeChannel(MyStrategy{},0);
spown_task(fibonacci_n(10, std::move(sender)));
for(;;){
auto ret = co_await recver->recv();
if(!ret) break;
std::cout<<*ret< fibonacci_seq(std::shared_ptr> ret_ch, std::shared_ptr> quit_ch){
int x=0;
int y=1;
for(;;){
auto [ret, quit] = co_await select(ret_ch->send(x), quit_ch->recv());
if(ret){
int next = x+y;
x=y;
y=next;
}else if(quit){
break;
}
}
}
cfn::Task<> example3(){
auto [ret_send,ret_recv] = cfn::makeChannel(MyStrategy{},0);
auto [quit_send,quit_recv] = cfn::makeChannel(MyStrategy{},0);
spown_task(fibonacci_seq(std::move(ret_send), std::move(quit_recv)));
for(int i=0;i<10;++i){
auto x = co_await ret_recv->recv();
std::cout<<*x<
```C++
template
class Task {
public:
auto operator co_await();
using promise_type = ;
};
```
`Task` は コルーチンによる非同期実行をサポートするクラスである
1. `co_await チャンネル` のように記述することで非同期処理を行うことができる
2. `co_return` を用いて 型`T`の値をコルーチンから返すことができる
3. `Task` の `co_await`に`Task`を渡すことで、`co_return` の結果を受け取ることができる
4. `Goroutine`に渡すことで実行できるようになる
```C++
// 1
auto [sender, recver] = cfn::makeChannel(MyAppStrategy{gbts}, 0);
auto t =
[](auto sender) -> cfn::Task<> {
for(int i=0;i<10;++i){
co_await sender->send(i);
}
}(sender);
```
```C++
// 2,3
cfn::Task task2(){
co_return 42;
};
cfn::Task<> task1(){
auto n = co_await task2();
std::cout< concept ChannelStrategy = requires(T strategy) {
// - require: thread safe
strategy.post_goroutine(std::declval>());
};
template
class BasicChannel{
public:
template SendAwaiter send(T && val);
RecvAwaiter send();
void close();
};
template
class Sender {
public:
template auto send(T &&val);
};
template
class Recver {
public:
auto recv();
};
template
std::tuple>,
std::shared_ptr>>
makeChannel(Strategy strategy, std::size_t n);
```
チャンネルは非同期通信をサポートする
- makeChannelは queue size nのチャンネルを生成し、それへの参照をもつ SenderとRecverを返す
- Senderはデストラクト時にChannelをcloseする
- Channelを使用するにはChannelStrategy を定義する必要がある。詳しくは 準備 の項を参照
```C++
auto [sender, recver] = cfn::makeChannel(my_strategy, 0);
auto t1 =
[](auto sender) -> cfn::Task<> {
for(int i=0;i<10;++i){
co_await sender->send(i);
}
}(sender);
auto t2 =
[&](auto recver) -> cfn::Task<> {
for(;;){
auto r = co_await recver->recv();
if(!r)break;
tmp.push_back(*r);
}
}(recver);
```
### select()
selectは複数のchannel.send()/recv()を受け取る。いずれかのchannelで値取得可能になり次第、そのchannelから値を取得する
```C++
auto [done, ret] =
co_await cfn::select(done_ch->recv(), ch->recv());
if(done){
break;
}else if(ret){
std::cout<<**ret<recv(), ch->recv());
if(done){
break;
}else if(ret){
std::cout<<**ret< &&t);
void execute();
};
```
### concept ChannelStrategy
```C++
template concept ChannelStrategy = requires(T strategy) {
strategy.post_goroutine(std::declval>());
};
```
- `void post_goroutine(cfn::Goroutine &&)` を実装する必要がある
- この関数は Channelで blockされた GoroutineをSchedulerにpostするために使う
- 複数threadでChannnelを使用する場合は、`post_goroutine` はthread safeで無ければならない