Thread: Simple thread object model (my first post)

Threaded View

Previous Post Previous Post   Next Post Next 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.

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