3
votes

I'm curious if using the asynchronous read function in Boost ASIO would have any performance benefits compared to using a separate thread that does reads synchronously. The use case would be having to always listen for data from a remote host.

In the async case, I believe ioservice.run() will block the thread until there is data to be read. In the synchronous case, the boost::asio:read call will block until there is data to be read. Is there any advantage to using async reads? It seems like if the application needs to anything in the background while waiting for data, a separate thread will be also be needed when using async_read since ioservice_run() will block.

The code would be something like the following (may not compile, just trying to get the idea across):

Using async reads:

#include <iostream>
#include <boost/asio.hpp>

void read_handler(const boost::system::error_code &ec)
{
    //Read incoming message

    //Chain the callbacks
    socket.async_read(read_handler);
}

int main()
{
    std::string host = "192.168.1.3";
    std::string port = "8888";
    std::cout << "Attemping connection to host " << host << " on port " << port << std::endl;
    boost::asio::connect(tcpSocket, resolver.resolve({host, port}));
    std::cout << "Connected" << std::endl;

    socket.async_read(read_handler);

    //Will block until there is data to read?
    ioservice.run();
    return 0;
}

Using synchronous reads in a separate thread:

#include <thread>

void readThread()
{
    while(true)
    {
        //Will block until there is data to read
        boost::asio::read(tcpSocket, boost::asio::buffer(buffer, size));
    }
}


int main()
{

    std::string host = "192.168.1.3";
    std::string port = "8888";
    std::cout << "Attemping connection to host " << host << " on port " << port << std::endl;
    boost::asio::connect(tcpSocket, resolver.resolve({host, port}));
    std::cout << "Connected" << std::endl;

    std::thread r{readThread};

    return 0;
}

Thanks! Please forgive my inexperience with asio and networking :)

1
The network is the rate-determining step. Which API you use to drive it is almost irrelevant.user207421

1 Answers

4
votes

This is mostly a question of scaling. In ASIO, the io_service is your central I/O notification mechanism. It abstracts the low-level OS-specific notification methods such as dev/epoll, I/O-Completion Ports and kqueue.

Suppose you want to monitor thousands of simultaneous connections for incomming data. If you implemented a blocking server with one thread for each connection, you would end up with thousands of threads, each taking some resource overhead. It would be way more performant just to create threads roughly around the number of CPUs in your system and let each of those wait on multiple connections simultaneously. This can only be achieved if your read operations are asynchronous, where one says that the read operation "runs in the background" and ASIO will notify you once there is a completion, along with the necessary information on which socket completed with what result. This model is called "proactive" and it is implemented like this for example in WinSock.

Another reason for asynchronous reads, especially for WinSock, beeing more efficient than synchronous ones, can be seen with high-throughput servers. There, having multiple outstanding receive operations will lock the receive buffers in the kernel, making sure there is always space available for receiving data without being forced to fall back on the internal receive buffers (such as SO_RCVBUF) which, if full, cause packet loss (UDP) or shrinks the TCP window, reducing the throughput either way.

Linux works differently (in a reactive manner), where the second reason doesn't count. The notification argument, however, still holds.