Wednesday, April 1, 2009

Serialize class member function invocations in C++ with Boost.Thread

Here's the scenario, you have a class that wraps the communication through one USB port, with each member sending one application-specific command(e.g. start/stop device, turn on LED etc ). Usually, the invocations of these member functions need to be mutual exclusive due to the fact that the USB port can only work on one thing at one time. This is not a problem in a single-threaded environment, but a real challenge in a multi-threaded word: what if you have a always-running thread of one member function being dedicated to polling the device, while you need to send other commands to the device on demand?

As this example shows, they will very likely step on each other's feet.



#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>

using std::cout;

class USB
{
public:

void poll()
{
cout << "poll@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
void start()
{
cout << "start@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void stop()
{
cout << "stop@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void pause()
{
cout << "pause@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
};

int main(int argc, char* argv[])
{
USB usb;
boost::thread_group threads;

threads.add_thread(new boost::thread(&USB::start, &usb));
threads.add_thread(new boost::thread(&USB::poll, &usb));
threads.add_thread(new boost::thread(&USB::pause, &usb));
threads.add_thread(new boost::thread(&USB::stop, &usb));

threads.join_all();
}

// outputs:

//D:\>usb
//start@thread=poll@thread=pause@thread=stop@thread=00143318
//001432E8001432B800143288
//
//
//
//
//D:\>




The solution is quite simple: add a mutex as a class member so that you can make any two functions mutual exclusive:


#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>

using std::cout;

class USB
{
public:

void poll()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "poll@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
void start()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "start@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void stop()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "stop@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void pause()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "pause@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
private:
boost::mutex m_mutex;
};

int main(int argc, char* argv[])
{
USB usb;
boost::thread_group threads;

threads.add_thread(new boost::thread(&USB::start, &usb));
threads.add_thread(new boost::thread(&USB::poll, &usb));
threads.add_thread(new boost::thread(&USB::pause, &usb));
threads.add_thread(new boost::thread(&USB::stop, &usb));

threads.join_all();
}

// outputs:

//D:\>usb
//start@thread=00143288
//poll@thread=001432B8
//pause@thread=001432E8
//stop@thread=00143318
//
//D:\>


It's also possible to only make just two functions mutual exclusive:

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>

using std::cout;

class USB
{
public:

void poll()
{
//boost::mutex::scoped_lock l(m_mutex);

cout << "poll@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
void start()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "start@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void stop()
{
boost::mutex::scoped_lock l(m_mutex);

cout << "stop@thread="
<< boost::this_thread::get_id()
<< std::endl;
}

void pause()
{
//boost::mutex::scoped_lock l(m_mutex);

cout << "pause@thread="
<< boost::this_thread::get_id()
<< std::endl;
}
private:
boost::mutex m_mutex;
};

int main(int argc, char* argv[])
{
USB usb;
boost::thread_group threads;

threads.add_thread(new boost::thread(&USB::start, &usb));
threads.add_thread(new boost::thread(&USB::poll, &usb));
threads.add_thread(new boost::thread(&USB::pause, &usb));
threads.add_thread(new boost::thread(&USB::stop, &usb));

threads.join_all();
}

// outputs:
//D:\>usb
//start@thread=poll@thread=pause@thread=001432E8001432B800143288
//stop@thread=00143318
//
//
//
//D:\>


In the code above, only start() and stop() are mutual exclusive.

No comments:

Post a Comment