Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
What is Asio?
3.
Coroutines
3.1.
Member Functions
4.
Stackless Coroutine
4.1.
reenter
4.1.1.
Syntax
4.1.2.
Limitation
4.2.
yield
4.3.
fork
4.4.
Implementation of Stackless Coroutine
4.4.1.
Explanation
4.4.2.
Output
5.
Stackful Coroutine
5.1.
Implementation of Stackful Coroutine 
5.2.
Explanation
5.3.
Output
6.
Frequently Asked Questions
6.1.
What is Asio?
6.2.
How are asynchronous operations different from synchronous operations?
6.3.
What are coroutine and subroutine?
6.4.
What is the use of the Asio library?
7.
Conclusion
Last Updated: Mar 27, 2024
Medium

What are the Stackless and Stackful Coroutines?

Introduction

Hey Ninjas! We have learned various topics in Asio. Asio is a C++ library that is used for networking. In this article, we will be learning about the coroutines in Asio. Coroutines are computer program components used for multitasking. There are two types of coroutines, stackful and stackless coroutines. We will discuss them in detail.

What are the stackless & stackful coroutines?

The topics covered in this article will be coroutine and its member functions and types of coroutines that are stackless and stackful coroutines. Before discussing them, let’s start with learning Asio.

What is Asio?

Asio, asynchronous input-output, is an open-source, cross-platform C++ library. It is designed for input, output, and networking. It was developed in 2003 by Christopher M. Kohlhoff and was released under Boost Software License. It provides an asynchronous model to the developers using a modern approach of C++. It is supported by a huge number of platforms like Windows, macOS, Linux, etc.

C++ logo

Now let’s start by learning about coroutines.

Coroutines

Coroutines are the components of a computer program that allows non-preemptive multitasking by generalizing subroutines. Subroutines are the sequence of instructions that perform some task for the main routine. The coroutines suspend or resume the execution of the programs. They are best suited for performing cooperative tasks, iterators, exceptions, event loops, pipes, and infinite lists.

Asio provides a coroutine class. This coroutine class in Asio is used to implement the stackless coroutines.

Member Functions

The member functions of the coroutine class are explained in the following table with their syntax.

member functions

Now let’s see the types of coroutines- stackless and stackful coroutines.

Also read, Abstract Data Types in C++ and Application of Oops

Stackless Coroutine

The stackless coroutine is implemented using a coroutine class. The following are some important points regarding stackless coroutine:

🔶 It is used to implement the asynchronous logic in the programs such that the overhead is minimal.

🔶 It is done by running the logic in a synchronous manner. Synchronous means occurring at the same time, and asynchronous refers to something which is not occurring simultaneously. 

🔶 The coroutine class stores the current state of the coroutine. 

🔶 The three pseudo-keywords (English language words used to explain an algorithm) are used with the coroutine class, i.e., reenter, yield, and fork. 

 

The preprocessor macros (reenter, yield, fork) are implemented by using switch statements in a similar fashion to Duff’s device. Let’s discuss these:

reenter

This macro defines the body of a coroutine. A pointer or a reference to a coroutine object is given as the parameter. The syntax for the same is shown below.

Syntax

For the coroutine object as the base class, you can write this: 

reenter(this)
{
    ... coroutine body ...
}

Replace this with coro_ if the other variable or data member is the base class.

reenter (coro_)
{
  ... coroutine body ...
}

Limitation

The switch is used to implement the reenter macro, which means the local variables inside the coroutine body should be taken care of. They are not allowed in the position when the variables can be bypassed while reentering the coroutine.

yield

The yield macro saves the current state of the coroutine. Then it initiates the asynchronous operation and defines the resume point. Last, control is transferred to the end of the coroutine body.

The most commonly used yield statement is:

yield socket_->async_read_some(buffer(*buffer_), *this);

To explicitly terminate the coroutine, a yield break; is used with majorly two steps.

🔷 First, the coroutine state is set to indicate the termination

🔷 Second, the control is transferred to the end of the coroutine body.

With the completion of the asynchronous operations, the function object is invoked, and the control is transferred to the resume point because of the reenter. The coroutine state forward must be carried with the asynchronous operation.

fork

This pseudo-keyword helps to fork a coroutine which means to split the coroutine into two or more copies. These steps are followed:

🔷 First, the current state of the coroutine is saved.

🔷 The copy of the coroutine is created by the fork, and it either immediately executes it or can schedule it for later.

🔷 After creating a copy, it defines the resume point immediately.

🔷 The control for the ‘parent’ continues from the next line.

 

The current state of the coroutines is saved using a fork, and a copy of the coroutine is created using this statement.

fork server(*this)();

The execution takes place immediately or is scheduled for later. The resume point for the execution is defined by it, and control is transferred immediately to the next line.

Let’s see a working example of implementing reenter and yield.

Implementation of Stackless Coroutine

#include <iostream>
#include <thread>
#include <chrono>
#include <asio/coroutine.hpp>
#include <asio/yield.hpp>
#include <cstdio>

using namespace std::chrono_literals;
using coroutine = asio::coroutine;
using namespace std;

void ninja(coroutine& c, int& result) {
    std::thread([&]() {
        std::this_thread::sleep_for(6s);
    result = 20;
    std::printf("%d\n", result);
        }).detach();
    std:cout << "Inside Ninja!" << endl;
}

int main(int, const char**) {
    coroutine c;
    int result = 100;
    reenter(c) {
        yield ninja(c, result);
    }
std:cout << result << endl;
    std::thread([]() {
        std::this_thread::sleep_for(2s);
        }).join();
        return 0;
}
You can also try this code with Online C++ Compiler
Run Code

Explanation

The use of yield macro is shown in the above program. A function called ninja is created in which an instance of the coroutine and an integer variable result is passed. The thread is used to pause the execution for 6 seconds. In the main function, an instance of the coroutine is created, and the reenter is used to define the body of the coroutine. The yield is used to call the function ninja as an asynchronous operation.

Output

output

Stackful Coroutine

The stackful coroutines have their stack that is used to process function calls. In stackful coroutines, there is a high-level spawn() function that is responsible for running stackful coroutines. This function allows the implementation of asynchronous logic in a synchronous manner.

The spawn function requires two parameters.

🔶 The first one is an executor or execution context. This is the context in which the execution of the coroutine takes place. For example, explicit synchronization is not required if multiple coroutines of the server’s per-client object run on the same strand.

🔶 Second is a function object to specify which part of the coroutine runs the code. The following is its syntax: 

void coroutine(asio::yield_context yield);

The yield parameter can also be passed to an asynchronous operation instead of the completion handler, as shown below:

std::size_t length = my_socket.async_read_some(asio::buffer(data), yield);
You can also try this code with Online C++ Compiler
Run Code

This suspends the coroutine and starts the asynchronous operation. When the asynchronous operation finishes, the coroutine automatically resumes. The asynchronous operation handler has the signature of the form:

void handler(asio::error_code ec, result_type result);
You can also try this code with Online C++ Compiler
Run Code

In case of the failure of asynchronous operation, the error_code is converted to system_error exception and thrown. The initiating function returns void, and the system_error exception is provided in terms of this error. The handler has the following form.

void handler(asio::error_code ec);
You can also try this code with Online C++ Compiler
Run Code

Let’s look at a working example of spawn funtion in Stackful Coroutine to understand it much better.

Implementation of Stackful Coroutine 

#include <iostream>
#include <asio/co_spawn.hpp>
#include <asio/detached.hpp>
#include <asio/io_context.hpp>
#include <asio/awaitable.hpp>
#include <asio/use_awaitable.hpp>

using namespace std;
using asio::detached;
using asio::use_awaitable;


asio::awaitable<void> ninja()
{
    auto executor = co_await asio::this_coro::executor;
    std::cout << "Printing Executor ";
    std::cout << executor << endl;
    std::cout << "Inside awaitable ninja function" << endl;
}

int main()
{
    asio::io_context io;
    asio::co_spawn(io, ninja(), detached);
    std::cout << "Implementing the spawn function in stackful coroutine" << endl;
    io.run();
    std::cout << "Working Fine" << endl;

    return 0;
}
You can also try this code with Online C++ Compiler
Run Code

Explanation

This example is used to show the working of the spawn function in a stackful coroutine. The necessary header files to implement spawn() are included in the code. An awaitable function named ninja is made. An executor is made inside the class and is shown using a print statement. In the main function, a context is created. It calls the ninja function using the spawn function. The detached() is used as a completion token to indicate the detached asynchronous operation.

Output

output

Frequently Asked Questions

What is Asio?

Asio is a cross-platform, open-source C++ library. It is an underrated C++ library designed for input, output, and networking purposes. Asio stands for asynchronous input-output. Christopher M. Kohlhoff developed Asio in 2003.

How are asynchronous operations different from synchronous operations?

The operations and programs in asynchronous operations can run in parallel, while the programs in synchronous operations will run one at a time. The asynchronous operations are non-blocking, while the synchronous operation is blocking.

What are coroutine and subroutine?

The coroutines are used for multitasking the processes and control the program periodically whenever many applications run simultaneously. The coroutines are generalizations of subroutines, and there are many entry points. The subroutine is a block of code that performs a specific task and can be called from other parts of a program.

What is the use of the Asio library?

Asio helps in doing network programming using C++ language. It allows the processing of data asynchronously by providing asynchronous I/O models.

Conclusion

This article was about Asio, which is a C++ library for networking and communication. We have discussed the coroutines in this article and their types, Stackless and Stackful coroutines.

You can check out our other articles if you want to dive deep into other concepts related to Asio -

👉 Asio Timers 

👉 SSL Support in Asio

👉 TCP, UDP, and ICMP Networking

 

You can refer to our guided paths on the Coding Ninjas Studio platform to learn more about DSADBMSCompetitive ProgrammingPythonJavaJavaScript, etc. To practice and improve yourself in the interview, you can also check out Top 100 SQL problemsInterview experienceCoding interview questionsand the Ultimate guide path for interviews. Do upvote our blog to help other ninjas grow.

Happy Coding !!

Live masterclass