Event / Task Queue Multithreading C++

前端 未结 8 1105
梦如初夏
梦如初夏 2021-02-04 15:18

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

8条回答
  •  后悔当初
    2021-02-04 15:48

    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 
    
    using namespace std;
    
    class GThreadObject
    {
    
        template 
        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 jobQueue;
        void functionTwoInternal(int argTwo, int arg2);
        void functionOneInternal(char * argOne, int argTwo);
    
    };
    

    GThreadObject.cpp

    #include 
    #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);
            newFuncPtr = (void (GThreadObject::*)(VariableSizeContainter))receivedEvent->funcPtr;
    
            //Execute the function
            (*this.*newFuncPtr)(*((VariableSizeContainter*)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 
    #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 > jobQueue;
    
    void GThreadObjectBoost::functionOne(char * argOne, int argTwo)
    {
        jobQueue.push(boost::bind(>hreadObjectBoost::functionOneInternal, this, argOne, argTwo));
    
        workerThread();
    }
    
    void GThreadObjectBoost::workerThread()
    {
        boost::function 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.

提交回复
热议问题