KMThreadPool: 4 – Thread Pool Step 2: SettingUp the Threads

来源:互联网 发布:pc游戏优化不好 编辑:程序博客网 时间:2024/05/29 18:03

4 – Thread Pool Step 2: SettingUp the Threads

KMTask

Lets go back to the heart of thethread pool (or to those in denial, the liver): The tasks. To keep everythingmanaged and such, we’ve encapsulated theKMTaskFunc and IKMTaskData* inside the KMTask class, just so we can keep things managed(and modular; so you can add new functionality like dependencies or referencesor contexts or whatever you’d like in the future).

class KMTask
{
private:
         KMTaskFunc         m_task;
         IKMTaskData*         m_data;
public:
         KMTask(KMTaskFunc task, IKMTaskData* data)
         {
                  m_task = task;
                  m_data = data;
         }
         ~KMTask()
         {
                  if(m_data!=NULL)
                       delete m_data;
                  m_data = NULL;
                  m_task = NULL;
         }
         KMTaskFunc         GetTask()         { return m_task; }
         IKMTaskData*         GetData()         { return m_data; }
protected:
};

Not so bad. Just manages the datafor us. This way we don’t really have to explicitly delete theIKMTaskData* inside our task.

KMThread

Here is the logic behind ourthreads: 1. Create the thread when the threadpool starts up, 2. Idle until wefind a task in the thread pool, 3. Run the task, 4. When we finish, clean upthe memory, 5. Goto 2 (6. Beat up the guy who wrote this tutorial for using agoto).

Here is a quick rundown of themembers in the threads:

KMThreadpool*          m_pthreadpool;
 
HANDLE                 m_hthread;
KMLock                 m_lock;
KMTask*                m_ptask;
 
bool                   m_brunning;
bool                   m_bpaused;
unsigned int           m_uithreadID;
DWORD                  m_dwexit;

m_pthreadpool is apointer to the actual thread pool class, and you’ll see why we need that injust a minute.
m_hthread is a handle (a kind ofsmart pointer) to the thread in the Windows API.
m_lock is the oneone lock we need to protect executing our task with.
m_ptask is thetask we get from the thread pool.
m_brunning is a flagthat’s set to true once the thread initializes successfully, and false if itfails or gets shut down.
m_bpaused is… uhh…legacy code! (meaning: code I forgot to delete, but isn’t breaking anything)
m_uithreadID is anID assigned from the Windows API.
m_dwexit is theexit code returned once the thread shuts down for any reason.

Now there are a few accessorfunctions and such, but we want to focus on 4 main functions:

public:
         void Begin();
         void End();
         DWORD ThreadProc();
protected:
         static unsigned __stdcall cThreadProc(LPVOID _pThis)
         {
                  return ((KMThread*)_pThis)->ThreadProc();
         }

Now, you see cThreadProc(). This function is called by the WindowsAPI when you create the thread. The API will be calling this function, and thisfunction callsThreadProc(), and ThreadProc() runs our task. Indirection out thewazzoo, baby!

Next we have Begin(). Nothing too special here for anyone whohas ever created threads before. We grab the instance of our thread pool(because it’s a singleton), then we create our thread using_beginthreadex(), and if m_hthread isn’t NULL, then it was a success! Once again, verysimple.

void KMThread::Begin()
{
         // Set our thread pool
         m_pthreadpool = KMThreadpool::getInstance();
#if defined( _WIN32 )
         // Start the thread.
         m_hthread = (HANDLE)_beginthreadex( NULL,
                  0,
                  &cThreadProc,
                  (void*)this,
                  0,
                  &m_uithreadID );
         m_brunning = true;
         if( m_hthread == NULL )
         {
                  // You can add extra error-handling here.
                  m_brunning = false;
         }
#endif /* defined( _WIN32 ) */
}

And for every Begin(), there must be an End().

void KMThread::End()
{
#if defined( _WIN32 )
         if( m_hthread != NULL )
         {
                  m_brunning = false;
                  WaitForSingleObject( m_hthread, INFINITE );
                  DWORD ExitCode;
                  GetExitCodeThread( m_hthread, &ExitCode );
                  m_dwexit = ExitCode;
                  CloseHandle( m_hthread );
                  m_hthread = NULL;
         }
#endif /* defined( _WIN32 ) */
}

People familiar with somemultithreading experience may throw up a red flag and say “Hey! Why aren’t youusing_endthreadex()? Duh!” Well, Mr. NonBeliever, _endthreadex() in the Win32 API doesnot closethe handle. This explicit way of closing the thread allows me to free up allnecessary memory and allows me to get the exit codes and such. And for newbiesto threading, thatWaitForSingleObject() function is also part of the Win32 API.It waits for the handle to the thread to finish its current cycle (for anINFINITE amount of time), and stops it fromcontinuing so we can manipulate the handle (in this case, closing the handle).

The last major function in thisclass is the full-on assembily of the circulatory system of the thread pool:TheThreadProc() function. This function is in charge of processing the actual task andgrabbing a new task once we finish. However, it’s not as simple as just run thetask and get a new one from the queue. We have to be safe and slick when we dothis. Here’s a look at the code:

DWORD KMThread::ThreadProc()
{
         m_ptask = NULL;
         // The main thread-loop. As long as this loop
         // is running, the thread stays alive.
         while(m_brunning)
         {
                  Sleep(1);
                  // The thread pauses when it finishes a task.
                  // Adding a task resumes it.
                  if(m_ptask != NULL)
                  {
                       m_lock.Lock();
                       {
                                KMTaskFunc task = m_ptask->GetTask();
                                IKMTaskData* data = m_ptask->GetData();
                                // Run the actual task
                                if(task != NULL && data != NULL)
                                {
                                     task(data);
                                }
                                // Task is complete.
                                delete m_ptask;
                                m_ptask = NULL;
                       }
                       m_lock.Unlock();
                  }
                  // If we're finished with our task, grab a new one.
                  if(m_ptask == NULL && m_pthreadpool->IsProcessing() == true)
                  {
                       m_ptask = m_pthreadpool->m_qtaskList.pop();
                  }
         }
         return 0;
}

The thread is almost set up asits own program, with the main thread loop in there. It’s set up so as long asthe thread is running (set by eitherbegin(), end() or by the thread pool), it will do thework inside.
First thing we do inside this loop is Sleep(1). When I originally wrote the thread pool,I wasn’t callingSleep(), and all my threads became processor hogs. If you downloaded the code orare writing it yourself, go ahead and comment out theSleep() call and watch your computer burn!

Next, we check to see if the m_ptask is NULL.

  • If it isn’t, then we go ahead and process it. We lock around the tasks to protect the data from getting possibly modified by another thread, and after we call the task (like you would with any normal function) we go and clean up the data.
  • If it is, check the thread pool and get the next task in the queue.

And that’s our threads! Nifty!Now on to the main brain of the outfit… The interface for all thismultithreaded goodness…

 

转自:http://keithmaggio.wordpress.com/code/c-win32-thread-pool-manager/4-step2/

原创粉丝点击