I would like to create a class whose methods can be called from multiple threads. but instead of executing the method in the thread from which it was called, it should perform t
Below is an implementation which doesn't require a "functionProxy" method. Even though it is easier to add new methods, it's still messy.
Boost::Bind and "Futures" do seem like they would tidy a lot of this up. I guess I'll have a look at the boost code and see how it works. Thanks for your suggestions everyone.
GThreadObject.h
#include <queue>
using namespace std;
class GThreadObject
{
template <int size>
class VariableSizeContainter
{
char data[size];
};
class event
{
public:
void (GThreadObject::*funcPtr)(void *);
int dataSize;
char * data;
};
public:
void functionOne(char * argOne, int argTwo);
void functionTwo(int argTwo, int arg2);
private:
void newEvent(void (GThreadObject::*)(void*), unsigned int argStart, int argSize);
void workerThread();
queue<GThreadObject::event*> jobQueue;
void functionTwoInternal(int argTwo, int arg2);
void functionOneInternal(char * argOne, int argTwo);
};
GThreadObject.cpp
#include <iostream>
#include "GThreadObject.h"
using namespace std;
/* On a continuous loop, reading tasks from queue
* When a new event is received it executes the attached function pointer
* Thread code removed to decrease clutter
*/
void GThreadObject::workerThread()
{
//New Event added, process it
GThreadObject::event * receivedEvent = jobQueue.front();
/* Create an object the size of the stack the function is expecting, then cast the function to accept this object as an argument.
* This is the bit i would like to remove
* Only supports 8 byte argument size e.g 2 int's OR pointer + int OR myObject8bytesSize
* Subsequent data sizes would need to be added with an else if
* */
if (receivedEvent->dataSize == 8)
{
const int size = 8;
void (GThreadObject::*newFuncPtr)(VariableSizeContainter<size>);
newFuncPtr = (void (GThreadObject::*)(VariableSizeContainter<size>))receivedEvent->funcPtr;
//Execute the function
(*this.*newFuncPtr)(*((VariableSizeContainter<size>*)receivedEvent->data));
}
//Clean up
free(receivedEvent->data);
delete receivedEvent;
}
void GThreadObject::newEvent(void (GThreadObject::*funcPtr)(void*), unsigned int argStart, int argSize)
{
//Malloc an object the size of the function arguments
void * myData = malloc(argSize);
//Copy the data passed to this function into the buffer
memcpy(myData, (char*)argStart, argSize);
//Create the event and push it on to the queue
GThreadObject::event * myEvent = new event;
myEvent->data = (char*)myData;
myEvent->dataSize = argSize;
myEvent->funcPtr = funcPtr;
jobQueue.push(myEvent);
//This would be send a thread condition signal, replaced with a simple call here
this->workerThread();
}
/*
* This is the public interface, Can be called from child threads
* Instead of executing the event directly it adds it to a job queue
* Then the workerThread picks it up and executes all tasks on the same thread
*/
void GThreadObject::functionOne(char * argOne, int argTwo)
{
newEvent((void (GThreadObject::*)(void*))>hreadObject::functionOneInternal, (unsigned int)&argOne, sizeof(char*)+sizeof(int));
}
/*
* This handles the actual event
*/
void GThreadObject::functionOneInternal(char * argOne, int argTwo)
{
cout << "We've made it to functionOne Internal char*:" << argOne << " int:" << argTwo << endl;
//Now do the work
}
void GThreadObject::functionTwo(int argOne, int argTwo)
{
newEvent((void (GThreadObject::*)(void*))>hreadObject::functionTwoInternal, (unsigned int)&argOne, sizeof(int)+sizeof(int));
}
/*
* This handles the actual event
*/
void GThreadObject::functionTwoInternal(int argOne, int argTwo)
{
cout << "We've made it to functionTwo Internal arg1:" << argOne << " int:" << argTwo << endl;
}
main.cpp
#include <iostream>
#include "GThreadObject.h"
int main()
{
GThreadObject myObj;
myObj.functionOne("My Message", 23);
myObj.functionTwo(456, 23);
return 0;
}
Edit: Just for completeness I did an implementation with Boost::bind. Key Differences:
queue<boost::function<void ()> > jobQueue;
void GThreadObjectBoost::functionOne(char * argOne, int argTwo)
{
jobQueue.push(boost::bind(>hreadObjectBoost::functionOneInternal, this, argOne, argTwo));
workerThread();
}
void GThreadObjectBoost::workerThread()
{
boost::function<void ()> func = jobQueue.front();
func();
}
Using the boost implementation for 10,000,000 Iterations of functionOne() it took ~19sec. However the non boost implementation took only ~6.5 sec. So Approx 3x slower. I'm guessing finding a good non-locking queue will be the biggest performance bottle neck here. But it's still quite a big difference.
You can solve this by using Boost's Thread -library. Something like this (half-pseudo):
class GThreadObject
{
...
public:
GThreadObject()
: _done(false)
, _newJob(false)
, _thread(boost::bind(>hreadObject::workerThread, this))
{
}
~GThreadObject()
{
_done = true;
_thread.join();
}
void functionOne(char *argOne, int argTwo)
{
...
_jobQueue.push(myEvent);
{
boost::lock_guard l(_mutex);
_newJob = true;
}
_cond.notify_one();
}
private:
void workerThread()
{
while (!_done) {
boost::unique_lock l(_mutex);
while (!_newJob) {
cond.wait(l);
}
Event *receivedEvent = _jobQueue.front();
...
}
}
private:
volatile bool _done;
volatile bool _newJob;
boost::thread _thread;
boost::mutex _mutex;
boost::condition_variable _cond;
std::queue<Event*> _jobQueue;
};
Also, please note how RAII allow us to get this code smaller and better to manage.
Here's a class I wrote for a similar purpose (I use it for event handling but you could of course rename it to ActionQueue -- and rename its methods).
You use it like this:
With function you want to call: void foo (const int x, const int y) { /*...*/ }
And: EventQueue q;
q.AddEvent (boost::bind (foo, 10, 20));
In the worker thread
q.PlayOutEvents ();
Note: It should be fairly easy to add code to block on condition to avoid using up CPU cycles.
The code (Visual Studio 2003 with boost 1.34.1):
#pragma once
#include <boost/thread/recursive_mutex.hpp>
#include <boost/function.hpp>
#include <boost/signals.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <string>
using std::string;
// Records & plays out actions (closures) in a safe-thread manner.
class EventQueue
{
typedef boost::function <void ()> Event;
public:
const bool PlayOutEvents ()
{
// The copy is there to ensure there are no deadlocks.
const std::vector<Event> eventsCopy = PopEvents ();
BOOST_FOREACH (const Event& e, eventsCopy)
{
e ();
Sleep (0);
}
return eventsCopy.size () > 0;
}
void AddEvent (const Event& event)
{
Mutex::scoped_lock lock (myMutex);
myEvents.push_back (event);
}
protected:
const std::vector<Event> PopEvents ()
{
Mutex::scoped_lock lock (myMutex);
const std::vector<Event> eventsCopy = myEvents;
myEvents.clear ();
return eventsCopy;
}
private:
typedef boost::recursive_mutex Mutex;
Mutex myMutex;
std::vector <Event> myEvents;
};
I hope this helps. :)
Martin Bilski
The POCO library has something along the same lines called ActiveMethod (along with some related functionality e.g. ActiveResult) in the threading section. The source code is readily available and easily understood.
You might be interested in Active Object one of the ACE Patterns of the ACE framework.
As Nikolai pointed out futures are planned for standard C++ some time in the future (pun intended).
For extensibility and maintainability (and other -bilities) you could define an abstract class (or interface) for the "job" that thread is to perform. Then user(s) of your thread pool would implement this interface and give reference to the object to the thread pool. This is very similar to Symbian Active Object design: every AO subclasses CActive and have to implement methods such as Run() and Cancel().
For simplicity your interface (abstract class) might be as simple as:
class IJob
{
virtual Run()=0;
};
Then the thread pool, or single thread accepting requests would have something like:
class CThread
{
<...>
public:
void AddJob(IJob* iTask);
<...>
};
Naturally you would have multiple tasks that can have all kinds of extra setters / getters / attributes and whatever you need in any walk of life. However, the only must is to implement method Run(), which would perform the lengthy calculations:
class CDumbLoop : public IJob
{
public:
CDumbJob(int iCount) : m_Count(iCount) {};
~CDumbJob() {};
void Run()
{
// Do anything you want here
}
private:
int m_Count;
};