Thread: Simple thread object model (my first post)

  1. #1
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981

    Simple thread object model (my first post)

    I noticed some posts recently about creating threads to execute in the context of a non-static member function. Myself along with others gave the usual reply: "use a static member function".
    I also mentioned the possibilities of using pointer to member function semantics.
    Never having used pointer to member function semantics in a thread class design, I thought I'd give it a go.
    Click here to read up on pointer to member function semantics first if you like.
    Here is what I came up with.
    Comments welcome.
    Code:
    #include <windows.h>
    
    #include <iostream>
    #include <cassert>
    using namespace std;
    
    // forward decl.
    class Thread;
    
    //-----------------------------------------------------------------------------
    // ThreadProc_t - Thread function type used by the Thread class. All thread 
    //                functions return DWORD and take a Thread* parameter.
    typedef DWORD (WINAPI *ThreadProc_t)(Thread *thread);
    
    class Thread
    {
        ThreadProc_t m_threadproc;
        DWORD m_threadid;
        HANDLE m_hthread;
        LPVOID m_thread_data;
    
        HANDLE m_evExit;
    
        //-------------------------------------------------------------------------
        // Actual thread entry point where we call m_threadproc
        static DWORD WINAPI ThreadProc(LPVOID lpv)
        {
            Thread *t = reinterpret_cast<Thread*>(lpv);
            return t->m_threadproc(t);
        }//ThreadProc
    
    public:
        //-------------------------------------------------------------------------
        // enum used by SetPriorityClass() for type safety
        enum PriorityClass_t
        {
            class_normal   = NORMAL_PRIORITY_CLASS,
            class_idle     = IDLE_PRIORITY_CLASS,
            class_high     = HIGH_PRIORITY_CLASS,
            class_realtime = REALTIME_PRIORITY_CLASS,
        };//PriorityClass_t
    
        //-------------------------------------------------------------------------
        // enum used by SetPriority() for type safety
        enum Priority_t
        {
            priority_above_normal  = THREAD_PRIORITY_ABOVE_NORMAL,
            priority_below_normal  = THREAD_PRIORITY_BELOW_NORMAL,
            priority_highest       = THREAD_PRIORITY_HIGHEST,
            priority_idle          = THREAD_PRIORITY_IDLE,
            priority_lowest        = THREAD_PRIORITY_LOWEST,
            priority_normal        = THREAD_PRIORITY_NORMAL,
            priority_time_critical = THREAD_PRIORITY_TIME_CRITICAL,
        };//Priority_t
    
    public:
        //-------------------------------------------------------------------------
        // Thread constructor.
        //   threadproc - pointer to function of type ThreadProc_t to execute in
        //                the context of this thread.
        explicit Thread(ThreadProc_t threadproc)
            : m_threadproc(threadproc), m_threadid(0), m_hthread(NULL), 
              m_evExit(NULL), m_thread_data(NULL)
        {
            assert(m_threadproc != NULL);
            m_evExit = ::CreateEvent(NULL, FALSE, FALSE, NULL);
            assert(m_evExit != NULL);
        }//constructor
    
        //-------------------------------------------------------------------------
        // Destructor - If the thread is running, Stop(1000) is called. If still 
        //              running the thread is killed (not good). Make the timeout
        //              configurable if need be, but I prefer to stop the thread
        //              myself before allowing the destructor to be called.
        virtual ~Thread() 
        {
            if (m_hthread != NULL)
            {
                Stop(1000); // could make this configurable
                if (IsRunning())
                    Kill();
                ::CloseHandle(m_hthread);
            }//if
    
            ::CloseHandle(m_evExit);
        }//destructor
    
        //-------------------------------------------------------------------------
        // Use these methods to pass custom data to the thread.
        void SetThreadData(LPVOID data) {m_thread_data = data;}
        LPVOID GetThreadData() {return m_thread_data;}
    
        //-------------------------------------------------------------------------
        // HANDLE operator to the thread handle
        operator HANDLE() {return m_hthread;}
    
        //-------------------------------------------------------------------------
        // Get the thread id of the last thread started by this class.
        DWORD GetThreadID() const {return m_threadid;}
    
        //-------------------------------------------------------------------------
        // Get the exit event used by this class to notify the thread 
        // implementation that Stop() was called and should terminate.
        HANDLE GetExitEvent() const {return m_evExit;}
    
        //-------------------------------------------------------------------------
        // Returns false if the thread hasn't been created. Otherwise, the current
        // execution state of thread is returned. Note that this will return true 
        // if the thread is suspended.
        bool IsRunning() const
        {
            if (m_hthread == NULL)
                return false;
            return ::WaitForSingleObject(m_hthread, 0) == WAIT_TIMEOUT;
        }//IsRunning
    
        //-------------------------------------------------------------------------
        // Start the threads execution. Returns true if the thread is already 
        // running or if the thread was started successfully. Returns false if the
        // thread could not be created.
        //    create_suspended - If true, the thread is created and immediately 
        //                       suspended. ResumeThread() must be called to start
        //                       thread execution.
        bool Start(bool create_suspended = false) 
        {
            if (IsRunning())
                return true;
    
            if (m_hthread != NULL)
                ::CloseHandle(m_hthread);
    
            ::ResetEvent(m_evExit);
            
            DWORD flags = create_suspended ? CREATE_SUSPENDED : 0;
            m_hthread = ::CreateThread(NULL, 0, ThreadProc, this, 
                                       flags, &m_threadid);
            return m_hthread != NULL;
        }//Start
    
        //-------------------------------------------------------------------------
        // Stop the currently running thread's execution. First the exit event is
        // signaled then the thread is waited on for timeout milliseconds. Once 
        // thread execution stops, it can only be restarted by calling Start(). Use 
        // SuspendThread() and ResumeThread() to stop a thread's execution and have
        // it restart where it left of.
        // Returns true if the thread isn't currently running or if the thread 
        // exits within the given timeout. Use a timeout of INFINITE to wait 
        // forever for the thread to exit.
        // NOTE: The thread implementation must monitor the exit event to be 
        //       notified of when this method is called.
        bool Stop(DWORD timeout)
        {
            if (!IsRunning())
                return true;
            ::SetEvent(m_evExit);
            return ::WaitForSingleObject(m_hthread, timeout) == WAIT_OBJECT_0;
        }//Stop
    
        //-------------------------------------------------------------------------
        // Wait for thread execution to complete. Returns true if the thread 
        // execution has completed. Returns false if the thread was never started
        // or if the timeout occured before thread execution was complete.
        //    timeout - number of milliseconds to wait before giving up. If not
        //              specified, an infinite timeout is used.
        bool Join(DWORD timeout = INFINITE)
        {
            if (m_hthread == NULL)
                return false;
            return ::WaitForSingleObject(m_hthread, timeout) == WAIT_OBJECT_0;
        }//Join
    
        //-------------------------------------------------------------------------
        // Terminate the currently running thread. Returns true if the thread is 
        // not running or if the thread was terminated successfully.
        // NOTE: This is a dangerous function that should only be used in the most
        //       extreme cases. 
        bool Kill()
        {
            if (!IsRunning())
                return true;
            return ::TerminateThread(m_hthread, 0xDEADBEAF) == TRUE;
        }//Kill
    
        //-------------------------------------------------------------------------
        // Get the DWORD return value of the thread function. Returns false if the
        // thread was never started or the exit code could not be retrieved.
        //    exitcode - reference parameter to receive the exit code
        bool GetExitCode(DWORD &exitcode) 
        {
            if (m_hthread == NULL)
                return false;
            return ::GetExitCodeThread(m_hthread, &exitcode) == TRUE;
        }//GetExitCode
    
        //-------------------------------------------------------------------------
        // Set the priority class of the thread. Returns false if the thread isn't
        // running. Returns true if the priority class is set successfully.
        //    pc = A value from the PriorityClass_t enum
        bool SetPriorityClass(PriorityClass_t pc)
        {
            if (!IsRunning())
                return false;
            return ::SetPriorityClass(m_hthread, pc) == TRUE;
        }//SetPriorityClass
    
        //-------------------------------------------------------------------------
        // Set the threads priority. Returns false if the thread isn't running. 
        // Returns true if the proirity of the thread is set successfully.
        //    p - A value from the Priority_t enum.
        bool SetPriority(Priority_t p)
        {
            if (!IsRunning())
                return false;
            return ::SetThreadPriority(m_hthread, p) == TRUE;
        }//SetPriority
    };//Thread
    
    //-----------------------------------------------------------------------------
    //-----------------------------------------------------------------------------
    
    // Thread class capable of running a non-static class member function in the
    // context of a thread. This is achieved by pointer-to-member function 
    // semantics with the class type as a template argument. In order for a member
    // function to be used as a thread, it must return DWORD and take Thread* as a
    // parameter.
    //   OBJ_T - class type of the object containing the member function to be run
    //           as a thread.
    template<class OBJ_T>
    class MfnThread : public Thread
    {
        // type of this class
        typedef MfnThread<OBJ_T> MyType;
        
        // pointer to OBJ_T member function type
        typedef DWORD (OBJ_T::*MfnThreadFunc_t)(Thread*);
    
        OBJ_T *m_thread_obj;
        MfnThreadFunc_t m_pmfn;
    
        //-------------------------------------------------------------------------
        // Proxy method used by ThreadProc below.
        DWORD RunThread() 
        {
            return (m_thread_obj->*m_pmfn)(this);
        }//RunThread
            
        //-------------------------------------------------------------------------
        // Thread procedure passed to our base class, simply calls RunThread().
        static DWORD WINAPI ThreadProc(Thread *t)
        {
            return static_cast<MyType*>(t)->RunThread();
        }//ThreadProc
    
    public:
        //-------------------------------------------------------------------------
        // MfnThread constructor.
        //   obj - templated class type containing the method to be run in the 
        //         context of this thread.
        //   thread_func - pointer of member function of OBJ_T to be run in the
        //         context of this thread.
        explicit MfnThread(OBJ_T *obj, MfnThreadFunc_t thread_func)
            : Thread(ThreadProc), m_thread_obj(obj), m_pmfn(thread_func)
        {
            assert(m_thread_obj != NULL);
            assert(m_pmfn != NULL);
        }//constructor
    };//MfnThread
    
    
    // Test class for MfnThread
    class Foo
    {
        LONG n;
    
        DWORD thread_method_increment(Thread *t)
        {
            return InterlockedIncrement(&n);
        }//thread_method_increment
    
        DWORD thread_method_decrement(Thread *t)
        {
            return InterlockedDecrement(&n);
        }//thread_method_decrement
    
    public:
        Foo() : n(0) {}
    
        DWORD test1(Thread *t)
        {
            MfnThread<Foo> t1(this, &Foo::thread_method_increment),
                           t2(this, &Foo::thread_method_increment),
                           t3(this, &Foo::thread_method_increment),
                           t4(this, &Foo::thread_method_decrement),
                           t5(this, &Foo::thread_method_decrement);
            // start em all up
            t1.Start();
            t2.Start();
            t3.Start();
            t4.Start();
            t5.Start();
    
            // wait for em to finish
            t1.Join();
            t2.Join();
            t3.Join();
            t4.Join();
            t5.Join();
    
            // hopefully, n will be 1
            cout << "n is now " << n << endl;
            return n;
        }//test1
    };//Foo
    
    
    // Test thread function for Thread class
    DWORD WINAPI thread_function(Thread *t)
    {
        for (int n=0; n<3; n++)
        {
            cout << "Look at me!, I'm a thread with id = " << t->GetThreadID() 
                 << endl << flush;
            Sleep(750);
        }//for
    
        return 69;
    }//thread1
    
    
    int main()
    {
        //-----------------------------------------------------
        // Thread class test
        Thread t(thread_function);
    
        // start the thread
        t.Start();
    
        // wait for it to complete
        t.Join();
    
        DWORD exitcode;
        t.GetExitCode(exitcode);
    
        cout << "thread_function returned " << exitcode << endl << endl;
    
        //-----------------------------------------------------
        // MfnThread class test
        Foo f;
        f.test1(NULL);
        
        // now call "f.test1()" again but as a thread
        MfnThread<Foo> ft(&f, &Foo::test1); 
    
        ft.Start();
        ft.Join(); // n should be 2 now
    
        ft.GetExitCode(exitcode);
        cout << "ft returned " << exitcode << endl; 
        return 0;
    }//main
    A link to this thread should awnser some future postings.

    gg
    Last edited by Codeplug; 11-09-2003 at 10:30 AM.

  2. #2
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Works great if using a ms compiler, fails to compile if using MinGW (gcc 3.2.3) or bcc5.5 cmd line. The problematic part is:
    Code:
    MfnThread<Foo> t1(this,Foo::thread_method_increment),
                           t2(this,Foo::thread_method_increment),
                           t3(this,Foo::thread_method_increment),
                           t4(this,Foo::thread_method_decrement),
                           t5(this,Foo::thread_method_decrement);
    where both compilers report they can't find a matching function.

    Since a lot of people seem to use MinGW (with devcpp) and, to a lesser extent, bcc5.5 you'll probably find that referring such individuals to your example as it currently stands will raise a lot more questions than answers.

    Good work all the same.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  3. #3
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Thanks Ken, Microsoft will compile anything!!

    The proper way to specify a pointer to member function is to use address-of operator (&)
    Code:
    MfnThread<Foo> t1(this, &Foo::thread_method_increment),
                   t2(this, &Foo::thread_method_increment),
                   t3(this, &Foo::thread_method_increment),
                   t4(this, &Foo::thread_method_decrement),
                   t5(this, &Foo::thread_method_decrement);
    I'll edit the original post so it's good

    gg

  4. #4
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    >>I'll edit the original post so it's good<<

    And so it is: mingw and bcc5.5 compile and run your amended code ok now; I didn't recheck it against ms.

    Might be an idea to pm Hammer and request your example gets added to the FAQ?

    That way it will be easier to reference and easier for whomever needs it to avoid reading/using it, since nobody reads the FAQ anyway.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  5. #5
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Bump yourself up to an improved Win32 thread object model implementation in this thread.

    gg

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Is getting object from Thread local storage expensive?
    By 6tr6tr in forum C++ Programming
    Replies: 2
    Last Post: 04-21-2008, 08:08 AM
  2. How can an object know what thread it's in?
    By 6tr6tr in forum C++ Programming
    Replies: 13
    Last Post: 04-21-2008, 07:10 AM
  3. assign none to COM thread model
    By George2 in forum Windows Programming
    Replies: 2
    Last Post: 04-12-2008, 12:44 AM
  4. Object destroy itself?
    By cminusminus in forum C++ Programming
    Replies: 28
    Last Post: 03-27-2008, 01:08 AM
  5. ATL Control Full Object Model
    By 7thSeeker in forum C++ Programming
    Replies: 6
    Last Post: 05-07-2004, 12:13 PM