Coroutines and Coroutine2 of the C++ Boost library

Tip: After the article is written, the table of contents can be automatically generated. For how to generate it, please refer to the help document on the right.

Coroutine and C++ Boost library Coroutine2

  • Summary
    • Why not boost.coroutine?
  • Threads and coroutines
    • Why not introduce coroutines in the C++20 standard
    • C++ coroutine and golang’s goroutine
  • 2. Usage steps
    • 1. Import the library
    • 2. Core classes
    • single coroutine
    • Cross execution of two coroutines

Abstract

Coroutine is a lightweight concurrency unit. Compared with threads, it has a smaller memory footprint and more efficient scheduling, and is suitable for I/O-intensive and computing-intensive scenarios. Boost.Coroutine2 is a coroutine implementation, which is a special control flow that allows the execution of subroutines to be paused and resumed at certain points. It preserves local execution state and allows reentry into the subroutine multiple times. However, multiple instances of Boost.Coroutine2 are executed sequentially and do not support parallel execution. In terms of concurrency performance and resource utilization, Goroutine has better performance than Coroutine2. Therefore, when writing concurrent programs, it is recommended to choose an appropriate concurrency model and corresponding language mechanism based on actual needs. Boost.Coroutine2 is suitable for scenarios such as assistance with multiple tasks and generator mode.

Why not boost.coroutine?

The official boost team has stated that it is now deprecated and coroutine2 is recommended.

Threads and coroutines

thread

Threads are a concurrent execution mechanism provided by the operating system. It has the following characteristics:

Runs in system state and is scheduled by the operating system.
Thread instances run independently, share heap memory, and have their own stack space.
An initial thread can take up several megabytes of memory, even if it does nothing.
Switching between threads requires some overhead.
There is a complete synchronization lock and data sharing mechanism.

coroutine

Coroutines are lightweight concurrency models that have the following characteristics:

Runs in user mode and is scheduled by the programmer.
Heap memory and stack space are shared between coroutine instances.
An initial coroutine only takes up a few KB of memory.
Switching between coroutines has minimal overhead.
Developers need to implement the synchronization mechanism themselves.

Why not introduce C++20 standard coroutines

C++20 introduces the feature of stackless coroutines to prepare for better development of asynchronous programming. However, because this version of the coroutine implementation method has always been very controversial, and the coroutine passed the resolution very late, only three keywords are actually provided to support the coroutine implementation. If you really want to use the coroutine, you have to implement the coroutine yourself. Cheng library. Then there is no time to provide coroutine-related support libraries in C++20, such as std::generator, std::task, etc., which need to be provided in C++23. With the support of these libraries, you can basically use the standard The library’s coroutine, C++23 is too new, and the compiler support has not caught up yet. In addition, its implementation and use are very different from coroutine2, so I may continue to choose coroutine2.

C++ coroutine and golang’s goroutine

Coroutine2 is different from goroutine in that it cannot actually be parallelized

Goroutine is a lightweight concurrency unit in the Go language. It is managed and scheduled by the Go runtime system and can be executed in parallel on multi-core processors. Coroutine2 is a kind of coroutine, which is a lightweight thread that is scheduled and managed by the user code. It is usually only executed sequentially or alternately in one thread, and does not support parallel execution.

Goroutine has a smaller memory footprint and more efficient scheduling than Coroutine2, so it is better in terms of concurrency performance and resource utilization. The Go language provides a concise and efficient concurrent programming paradigm through mechanisms such as Goroutine and Channel, allowing developers to easily write concurrent programs.

It should be noted that although Coroutine2 itself does not support parallel execution, Coroutine2 can be combined into concurrent execution flows through some technical means, such as using thread pools or coroutine pools to execute Coroutine2 concurrently. However, the concurrency performance and resource utilization of this method may be reduced compared to Goroutine.

In this comparison, coroutine2’s implementation of coroutine seems to be useless, but in fact, the different behaviors of goroutine and coroutine also show that their application scenarios are also different:
Boost.Coroutine2 can be thought of as a special control flow provided by boost that allows the execution of subroutines to be paused and resumed at certain points. It preserves local execution state and allows reentrancy into a subroutine multiple times (useful if state must be maintained between function calls). For example (generators, collaborative multitasking) are all suitable scenarios. The code structure is very simple, and this part is better than goroutine.

2. Usage steps

1. Import library

Dependent library: Boost.Context
Language version required: C++11 version
Header file location: #include

2. Core classes

There are only two core classes in coroutine2: pull_type and push_type.

In Boost.Coroutine2, coroutines can only pass data in one direction, and data can only flow in one direction from one code block to another. The inflow and outflow correspond to the push_type and pull_type types respectively. These two types constitute the channel for jumps between coroutines and are also the channels for data transfer.

pull_type

pull_type is used to get data from the coroutine. It provides the following methods:
begin(): Returns an iterator that can iterate to obtain data in the coroutine.
get(): Get the next data in the coroutine.
operator(): Calls the next operation of the coroutine and returns whether the coroutine ends.

push_type

push_type is used to pass data to the coroutine. It provides the following methods:
operator(): Pass data to the coroutine and return whether the coroutine ends.

Single coroutine

 /*
* Fibonacci Sequence
*/
boost::coroutines2::coroutine<int>::pull_type source(
[ & amp;](boost::coroutines2::coroutine<int>::push_type & amp; sink) {<!-- -->
int first = 1, second = 1;
sink(first);
sink(second);
for (int i = 0; i<8; + + i) {<!-- -->
int third = first + second;
first = second;
second = third;
sink(third);
}
});

for (auto i : source)
{<!-- -->
std::cout << i << " ";

}

Output: 1 1 2 3 5 8 13 21 34 55

Two coroutines are executed crosswise

 boost::coroutines2::coroutine<int>::pull_type apull([](boost::coroutines2::coroutine<int>::push_type & amp; apush)
{<!-- -->
for (int i = 0; i < 10; i + + )
{<!-- -->
cout << "---------------------" << "coroutine 1" << endl;
apush(1);
}

}
);


boost::coroutines2::coroutine<int>::pull_type apull2([](boost::coroutines2::coroutine<int>::push_type & amp; apush)
{<!-- -->

for (int i = 0; i < 10; i + + )
{<!-- -->
cout << "---------------------" << "coroutine 2" << endl;
apush(2);
}

}
);

for (int i = 0; i < 10; + + i)
{<!-- -->
apull.get();
apull();
apull2.get();
apull2();
}
cout << "continue>>>" << endl;

Output:

---------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
--------------------------coroutine 1
--------------------------coroutine 2
continue>>>