I would like to implement a Boost Asio pattern using a thread for GUI and a worker thread for some socket IO.
The worker thread will use boost::asio::io_service
to manage a socket client. All operations on sockets will be performed by the worker thread only.
The GUI thread needs to send and receive messages from the worker thread.
I can't exactly figure how to implement this pattern using Boost Asio.
I've already implemented the socket communication in the standard Asio way (I call io_service.run()
from the worker thread and I use async_read_some
/async_send
). I don't need strands
because io_service.run()
is called from the worker thread only.
Now I'm trying to add the cross thread message queue. How I can I implement it?
Should I run
the io_service
from the GUI thread too?
Or should I just use strands
with post
to post messages from the GUI thread to the worker thread (without calling io_service.run()
or io_service.poll_one()
from the GUI thread), and use the operating system's GUI message loop to post messages from the worker thread to the GUI thread?
If I need to call io_service.run()
or io_service.poll_one()
from the GUI thread too, do I need to use strands
on the socket operations, since the io_service
is shared between two threads?
EDIT: to clarify my question, I would like to do whatever I can, to implement the message queue, using Boost Asio, relying on other libraries only if Boost Asio can't do the job.
If you have only one worker, then it's rather easy.
ASIO's handlers are executed by the thread(s) that are calling
io_service.run()
. In your case, that means that only one thread, the worker one, can execute callback handler. So you need not to worry about thread safety here.Your GUI thread, assuming that it has access to one's socket, can call
boost::asio::async_write()
without problem. The callback handler, however, will be executed in the worker thread.From my experience (admitedly limited), I used this pattern:
boost::asio::async_write()
: the worker thread will take care of it.boost::asio::async_read()
, and could be building "business logic packet". What I mean here, is that it construct meaningfull message (could be a subclass of a custom classPacket
orEvent
or w/e you what) from raw data.Let me know if its not clear / if I can be of more help.
Message passing is fairly generic. There are various ways to approach the problem, and the solution will likely be dependent on the desired behavioral details. For example, blocking or non-blocking, controlling memory allocation, context, etc.
Boost.Lockfree provides thread-safe lock-free non-blocking queues for singe/multi consumer/producers. It tends to lend itself fairly nicely to event loops, where it is not ideal for the consumer to be blocked, waiting for the producer to signal a synchronization construct.
Alternatively, Boost.Asio's
io_service
can function as a queue. The message just needs to be bound to the specified handler.This comment suggest that the desire is more than message passing. It sounds as though the end goal is to allow one thread to cause another thread to invoke arbitrary functions.
If this is the case, then consider:
io_service
to setup signal emissions. If the GUI thread and worker thread each have their ownio_service
, then the worker thread can post a handler into the GUI thread'sio_service
that will emit a signal. In the GUI thread's main loop, it will poll theio_service
, emit the signal, and cause slots to be invoked from within the GUI thread's context.Here is complete example where two threads pass a message (as an
unsigned int
) to one another, as well as causing arbitrary functions to be invoked within another thread.And its output:
The way that I exchange messages between 2+ threads is to use a container like a queue and store them in there and then use an event to notify the worker thread to wake up and process them. Here is an example:
In the header file:
The above code is for Microsoft VC++. You might have to use a different class or methods if your development environment is different. But, the idea should be the same.
Edit - More Complete Code Example
So, in answer to your question about whether you have to use a queue or not. In your comment to Xaqq, you said "I need to exchange messages between the two threads." So using a container like a queue is how messages can be passed to another thread for processing. If you don't like the STL containers, Boost does have some. As far as I know, there is no Boost ASIO internal container that can be accessed. Storing and passing the messages around is something you have to do in your code.
One last note about the call to io_service::run. It will only block while there is work to do. See this link. In my example code above, a work item is added to the io_service object before the run method is called, so it will block indefinitely - which is what I want. If I really wanted only one thread, then what I might do is set up the worker thread to call the run method with a work object so that it would block indefinitely. This would handle all asynchronous I/O coming from and going to the server. Inside the class, I would write an interface method or two so that the gui can send data to the server. These methods could use the async write .vs. the synch write method and thus would return right away - so your gui won't block long. You would need to write a HandleWrite method. My code does not do much with it - just logs an error if one occurs.