3

I am using Boost::ASIO version 1.52.0 for a Windows client. I would like to be able to dedicate a thread to handling all receiving messages from the server, and then another dedicated thread to handle all outgoing messages to the server. I am using the same io_service object for both threads right now. What I'm worried about is that when the io_service::run() method is called, the thread that is handling the outgoing messages, might get scheduled to handle some incoming message calls and vice-a-versa. So, my question - is this possible? If it is, then would using a second io_service object solve the problem - one for each thread? Is there a better way to design this? I am trying to avoid using multiple threads for both the read and write handlers.

The other thing I would like confirmation on is - I have read that a lock should be used to single thread the code if 2 or more async_reads can be done at the same time. Likewise for async_writes. Should a lock also be used if an async_read can execute at the same time as an async_write, or should that be thread safe?

One last question - can the async methods - async_connect, async_read, and async_write all be called from a different thread before a worker thread has called the io_service run method?

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
Bob Bryan
  • 3,687
  • 1
  • 32
  • 45

2 Answers2

4

You should use a single io_service, however many threads you use to invoke io_service::run() can also invoke handlers for asynchronous operations owned by the io_service. If these handlers access shared data structures, you will need to use a strand to ensure exclusive access. You'll also need to ensure at most one write operation

This operation is implemented in terms of zero or more calls to the stream's async_write_some function, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.

and read operation

This operation is implemented in terms of zero or more calls to the stream's async_read_some function, and is known as a composed operation. The program must ensure that the stream performs no other read operations (such as async_read, the stream's async_read_some function, or any other composed operations that perform reads) until this operation completes.

is outstanding for each socket.

Using an io_service for all async_write() operations and another io_service for all async_read() operations is not possible because a single socket is serviced by one io_service that is passed in as a parameter in the constructor.

In my experience most mult-io_service designs are driven by performance and latency requirements. The HTTP Server 2 example explores this with an io_service per CPU.

Community
  • 1
  • 1
Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • Thanks for the clarification on only using one io_service object per socket. The client I am writing will probably need to eventually use prioritized handlers then. I have already found the example for that. I was just hoping that I could avoid having to implement that since it seems a little involved. I was hoping that by dedicating a thread to incoming messages, and another thread to outgoing messages I could keep the outgoing message handling very responsive. Guess I will just try it with 2 threads, implement strands or locking, and hope for the best. – Bob Bryan Jan 24 '13 at 06:24
2

It is one of the failures with asynchronous libraries like ASIO and IOCP when you do not have multiple objects (ie sockets) to operate over. Like the common case of a single UDP socket that sends and receives many packets to and from many different endpoints.

In cases like that having a dedicated thread just blocking on read and quickly putting the data into a queue and one dedicated thread blocking on writing is much more efficient than async anything. You should be able to get over 100K packets per second on modern hardware that way. If you can use the sendmmsg or recvmmsg type functions you can get this number much higher by avoiding operating system call overhead. They allow you to bunch up messages, but last I checked that wasn't in ASIO and will require your own code.

In your example app because there is only ever one READ or WRITE handler active at a time the two threads should only do one or the other so you can avoid the need for strand there I believe. However if you move to 2 dedicated non asynchronous threads in the setup I just mentioned your performance will be a lot better.

Ryler Sturden
  • 354
  • 2
  • 9