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
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.
boost::lockfree::queue worker_message_queue;
void send_worker_message(const message_type& message)
{
// Add message to worker message queue.
worker_message_queue.push(message);
// Add work to worker_io_service that will process the queue.
worker_io_service.post(&process_message);
}
void process_message()
{
message_type message;
// If the message was not retrieved, then return early.
if (!worker_message_queue.pop(message)) return;
...
}
Alternatively, Boost.Asio's io_service
can function as a queue. The message just needs to be bound to the specified handler.
void send_worker_message(const message_type& message)
{
// Add work to worker_io_service that will process the message.
worker_io_service.post(boost::bind(&process_message, message));
}
void process_message(message_type& message)
{
...
}
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 own io_service
, then the worker thread can post a handler into the GUI thread's io_service
that will emit a signal. In the GUI thread's main loop, it will poll the io_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.
#include
#include
#include
#include
#include
/// @brief io_service dedicated to gui.
boost::asio::io_service gui_service;
/// @brief io_service dedicated to worker.
boost::asio::io_service worker_service;
/// @brief work to keep gui_service from stopping prematurely.
boost::optional gui_work;
/// @brief hello slot.
void hello(int x)
{
std::cout << "hello with " << x << " from thread " <<
boost::this_thread::get_id() << std::endl;
}
/// @brief world slot.
void world(int x)
{
std::cout << "world with " << x << " from thread " <<
boost::this_thread::get_id() << std::endl;
}
/// @brief Type for signals.
typedef boost::signals2::signal signal_type;
void emit_then_notify_gui(signal_type& signal, unsigned int x);
/// @brief Emit signals then message worker.
void emit_then_notify_worker(signal_type& signal, unsigned int x)
{
// Emit signal, causing registered slots to run within this thread.
signal(x);
// If x has been exhausted, then cause gui service to run out of work.
if (!x)
{
gui_work = boost::none;
}
// Otherwise, post work into worker service.
else
{
std::cout << "GUI thread: " << boost::this_thread::get_id() <<
" scheduling other thread to emit signals" << std::endl;
worker_service.post(boost::bind(
&emit_then_notify_gui,
boost::ref(signal), --x));
}
}
/// @brief Emit signals then message worker.
void emit_then_notify_gui(signal_type& signal, unsigned int x)
{
// Emit signal, causing registered slots to run within this thread.
signal(x);
// If x has been exhausted, then cause gui service to run out of work.
if (!x)
{
gui_work = boost::none;
}
// Otherwise, post more work into gui.
else
{
std::cout << "Worker thread: " << boost::this_thread::get_id() <<
" scheduling other thread to emit signals" << std::endl;
gui_service.post(boost::bind(
&emit_then_notify_worker,
boost::ref(signal), --x));
}
}
void worker_main()
{
std::cout << "Worker thread: " << boost::this_thread::get_id() << std::endl;
worker_service.run();
}
int main()
{
signal_type signal;
// Connect slots to signal.
signal.connect(&hello);
signal.connect(&world);
boost::optional worker_work(
boost::ref(worker_service));
gui_work = boost::in_place(boost::ref(gui_service));
std::cout << "GUI thread: " << boost::this_thread::get_id() << std::endl;
// Spawn off worker thread.
boost::thread worker_thread(&worker_main);
// Add work to worker.
worker_service.post(boost::bind(
&emit_then_notify_gui,
boost::ref(signal), 3));
// Mocked up GUI main loop.
while (!gui_service.stopped())
{
// Do other GUI actions.
// Perform message processing.
gui_service.poll_one();
}
// Cleanup.
worker_work = boost::none;
worker_thread.join();
}
And its output:
GUI thread: b7f2f6d0 Worker thread: b7f2eb90 hello with 3 from thread b7f2eb90 world with 3 from thread b7f2eb90 Worker thread: b7f2eb90 scheduling other thread to emit signals hello with 2 from thread b7f2f6d0 world with 2 from thread b7f2f6d0 GUI thread: b7f2f6d0 scheduling other thread to emit signals hello with 1 from thread b7f2eb90 world with 1 from thread b7f2eb90 Worker thread: b7f2eb90 scheduling other thread to emit signals hello with 0 from thread b7f2f6d0 world with 0 from thread b7f2f6d0