Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Thread
2.1.
Classes and Functions in Thread
2.1.1.
Implementation
2.1.2.
Explanation
2.1.3.
Output
3.
Buffer
3.1.
Classes and Functions in Buffer
3.1.1.
Implementation
3.1.2.
Explanation
3.1.3.
Output
4.
Strand
4.1.
Classes and Functions in Strand
4.1.1.
Implementation
4.1.2.
Explanation
4.1.3.
Output
5.
Frequently Asked Questions
5.1.
What is the full form of Asio?
5.2.
What is the use of the Asio library?
5.3.
What is a thread?
5.4.
What is a buffer?
5.5.
What is a strand?
6.
Conclusion
Last Updated: Mar 27, 2024
Medium

Thread, Buffers and Strands in Asio

Introduction

Do you know that Asio provides support for scatter-gather operations? Do you know about the concept of threads and how we can use them without a lock? Have you heard anything about buffers and strands in Asio? If not, then don’t worry. We will help you to clear all your doubts.

Introduction to thread, buffer and strands

In this article, we will discuss about threads, buffers, and strands. We will discuss important classes and functions that we can use in three of them. We will also make programs to understand each of them more clearly. Let’s understand what a thread is.

Also read, Abstract Data Types in C++

Thread

A thread can be defined as a lightweight process. A process can be divided into many threads, and each thread defines different execution paths. Threads help a process to run concurrently. The multiple threads call io_context::run() in order to set a pool of threads. The io_context is used to distribute work across each thread equivalently.

We must include the asio/io_context.hpp header in order to use the io_context class.

Classes and Functions in Thread

Thread use io_context class, which includes the below functions:

👉 get_executor() function helps to obtain the executor that is associated with the io_context. The executor helps to execute concurrent tasks.

👉 notify_fork() function notifies the execution context of a fork-related event. The execution context contains currently running code and all those things that are helpful in its execution.

👉 run() function is used to run the io_context object.

👉 restart() function is used to restart the io_context object.

👉 run_for() function is used to run the event processing loop of an io_context object for a specified duration.

👉 run_one_for() function is used to run the event processing loop of an io_context object for a specified duration in order to execute at most one handler.

👉 run_one_until() function is used to run the event processing loop of an io_context object until a specified time in order to execute at most one handler.

👉 run_until() function is used to run the event processing loop of an io_context object until a specified time.

👉 stop() function is used to stop the event processing loop of an io_context object.

👉 stopped() function helps in determining whether the io_context object is stopped or not.


Let’s make a program to understand it more clearly.

Implementation

#include<iostream>
#include<asio.hpp>
#include <asio/io_context.hpp>
 
using namespace std;

int main() {

    asio::io_context io;

    cout << "Running io_context object event processing loop using io.run() function..." <<"\n"<< endl;
    io.run();
 
    asio::ip::tcp::socket socket(io);
    auto executor = socket.get_executor();
    cout << "Printing socket executor " << executor << "\n" << endl;
    cout << "Stopping io_context object event processing loop using io.stop() function..." << "\n" << endl;
    io.stop();
    cout << "io_context object is stopped that's why stopped() function is returning " << io.stopped() << "\n" << endl;
 
    return 0;
}

Explanation

In the above code, we have included the header #include <asio/io_context.hpp> in order to use class io_context and its functions. We have used the run() function to run the io_context object. We have created a TCP connection and printing executor of the socket using get_ecxecutor() function. In the end, we are stopping the io_context object using io.stop(). We have also checked whether the io_context object is stopped or not using stopped() function.

Output

Output Screen

Moving forward, let's understand what a buffer is.

Buffer

The buffer can be defined as the contiguous memory regions where the data is stored temporarily. We can indicate a buffer as a tuple of a pointer and size. The size of the buffer is always in bytes. Asio provides support for scatter-gather operations in order to allow the development of network applications efficiently. The scatter-gather operations are useful in gathering data and scattering it into a given set of buffers. 

Classes and Functions in Buffer

There are a few classes that are useful in the buffer:

1️⃣const_buffer class helps in holding non-modified buffers. This class consists of data() and size() functions which are used to get a pointer and size of the memory range, respectively.

2️⃣Streambuf class is useful in combination with i/o streams. This class includes below functions:

👉 capacity() function is used to get the current capacity.

👉commit() function is helpful in moving characters from the output sequence to the input sequence.

👉consume() function is used to remove characters from the input sequence.

👉data() function is used to get a buffer list that represents the input sequence.

👉max_size() function is used for getting the maximum size.

👉prepare() function is used to get a buffer list that represents the output sequence.

👉size() function is used to get the input sequence size.

 

Two more important functions that we often use in buffers are:

➡️buffer_begin() function represents the beginning of the buffer.

➡️buffer_end() function represents the ending of the buffer.

Let’s make a program to better understand buffers.

Implementation

#include <iostream>
#include <asio.hpp>
#include <asio/buffer.hpp>
#include <asio/buffered_stream.hpp>
#include <asio/streambuf.hpp>
 
using namespace std;
 
int main()
{
	asio::io_context io;
	asio::random_access_file file(io);
	file.open("C:/code.cpp", asio::random_access_file::read_only);

	asio::streambuf buff;
	asio::streambuf::const_buffers_type buffer = buff.data();
 
	size_t bufferSize = file.size();
 
	cout << "Using begin() function. " << endl;
 
	asio::streambuf::const_buffers_type::const_iterator i = buffer.begin();
 
	while (i != buffer.end())
		{
			asio::const_buffer buf(*i++);
			bufferSize += buffer.size();
		}
	cout << "Used end() function. " << endl;
 
	cout << "Printing buffer size " << bufferSize << " bytes" << endl;
	cout << "Printing buffer data " << buffer.data() << endl;

	return 0;
}

Explanation

In the above code, we have used streambuf class and its functions. We have used the data() function to get a buffer list. We have opened a file for reading only. Then, we have taken a variable bufferSize in which we are storing the size of our opened file because a buffer size must include the size of opened file too. We have used begin() and end() functions to represent starting and ending of the buffer size.

Output

Output Screen

Moving forward, let's understand what a strand is.

Strand

A strand can be defined as a continuous invocation of event handlers. The strands allow a multithreaded program to execute without using the lock. The concept of locking in threads allows one thread to run at a time, and another thread can only be run once the thread that owns the lock gets free. The strands can either be implicit or explicit. Whenever we call one thread using io_context::run(), then in such cases, we can say that all event handlers are executing in an implicit strand. Whereas in an explicit strand, the event handler function objects have to be bound using aiso::bind_executor(). We must include the asio/strand.hpp header in our program in order to use the strand class and their functions.

Classes and Functions in Strand

The strand class consists of the below functions:

👉 context() function is used to obtain the execution context that is underlying.

👉 defer(), execute(), post(), and dispatch() functions can be used to request the strand in order to invoke the given function object.

👉 get_inner_executor() function is used to obtain the underlying executor.

👉 on_work_finished() function is helpful in informing the strand that there is some work.

👉 on_work_started() function is helpful in informing the strand about some work that it has to do.

👉 prefer() function is used in forwarding a preference to the underlying executor.

👉 query() function is used in forwarding a query to the underlying executor.

👉 require() function is used in forwarding a requirement to the underlying executor.

👉 running_in_this_thread() function is helpful in determining whether the strand is running in the current thread or not.

Let’s make a program to better understand strands.

Implementation

#include <asio/post.hpp>
#include <iostream>
#include <asio/io_service.hpp>
#include <asio/strand.hpp>

using namespace std;
 
class StrandPost
{
private:
    asio::io_service service_;
    asio::io_service::strand strand_;
    std::thread module_thread_;

    void run() {
        auto run_one = [this]() {
            std::cout << " Thread is running..." << std::endl;
            auto r = service_.run_one();
            return r;
        };
 
        while (run_one());
        std::cout << "Execution Completed" << std::endl;
    }

 
public:
    StrandPost(): service_(), strand_(service_), module_thread_(&StrandPost::run, this){}

    ~StrandPost() {service_.stop(); if (module_thread_.joinable()) {
            module_thread_.join();
        }
    }
    void Print(const std::string& string) {
        asio::post(strand_, [string]() {
            std::cout << string << std::endl;
            });
    }
};
 
int main() {

    StrandPost strand;
    std:cout << "Checking threads..." << endl;
    strand.Print("Hello Ninjas");
    strand.Print("How are you?");

    std::this_thread::sleep_for(std::chrono::seconds(4));
    return 0;
}

Explanation

In the above code, we have made a StrandPost class inside which we have run() function. We have used run_one() function inside run() function, the run_one() function is used to block other threads until a thread is not completed. In the below output you can see that after completing one thread and printing its output only, the other thread is processed.

Output

Output Screen

Also see, Application of Oops

Frequently Asked Questions

What is the full form of Asio?

Asio stands for Asynchronous Input Output.

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.

What is a thread?

A thread can be defined as one small part of a program that is executed. 

What is a buffer?

The buffer can be defined as the contiguous memory regions where the data is stored temporarily.

What is a strand?

A strand can be defined as a continuous invocation of event handlers. The strands allow a multithreaded program to execute without using locking.

Conclusion

In this article, we have discussed threads, buffers, and strands. We have discussed important classes and functions that we can use in three of them. We have also made programs to understand each of them more clearly. To learn more about the Asio library, you can read the below-mentioned articles:

We hope this article helped you in understanding threads, buffers, and strands. You can read more such articles on our platform, Coding Ninjas Studio. You will find articles on almost every topic on our platform. Also, you can practice coding questions at Coding Ninjas to crack good product-based companies. For interview preparations, you can read the Interview Experiences of popular companies

Happy Coding!

Live masterclass