4 – Thread Pool Step 2: Setting Up the Threads

KMTask

Lets go back to the heart of the thread pool (or to those in denial, the liver): The tasks. To keep everything managed and such, we’ve encapsulated the KMTaskFunc and IKMTaskData* inside the KMTask class, just so we can keep things managed (and modular; so you can add new functionality like dependencies or references or 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 data for us. This way we don’t really have to explicitly delete the IKMTaskData* inside our task.

KMThread

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

Here is a quick rundown of the members 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 a pointer to the actual thread pool class, and you’ll see why we need that in just a minute.
m_hthread is a handle (a kind of smart pointer) to the thread in the Windows API.
m_lock is the one one lock we need to protect executing our task with.
m_ptask is the task we get from the thread pool.
m_brunning is a flag that’s set to true once the thread initializes successfully, and false if it fails or gets shut down.
m_bpaused is… uhh… legacy code! (meaning: code I forgot to delete, but isn’t breaking anything)
m_uithreadID is an ID assigned from the Windows API.
m_dwexit is the exit code returned once the thread shuts down for any reason.

Now there are a few accessor functions 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 Windows API when you create the thread. The API will be calling this function, and this function calls ThreadProc(), and ThreadProc() runs our task. Indirection out the wazzoo, baby!

Next we have Begin(). Nothing too special here for anyone who has 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, very simple.

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 some multithreading experience may throw up a red flag and say “Hey! Why aren’t you using _endthreadex()? Duh!” Well, Mr. NonBeliever, _endthreadex() in the Win32 API does not close the handle. This explicit way of closing the thread allows me to free up all necessary memory and allows me to get the exit codes and such. And for newbies to threading, that WaitForSingleObject() function is also part of the Win32 API. It waits for the handle to the thread to finish its current cycle (for an INFINITE amount of time), and stops it from continuing so we can manipulate the handle (in this case, closing the handle).

The last major function in this class is the full-on assembily of the circulatory system of the thread pool: The ThreadProc() function. This function is in charge of processing the actual task and grabbing a new task once we finish. However, it’s not as simple as just run the task and get a new one from the queue. We have to be safe and slick when we do this. 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 as its own program, with the main thread loop in there. It’s set up so as long as the thread is running (set by either begin(), end() or by the thread pool), it will do the work inside.
First thing we do inside this loop is Sleep(1). When I originally wrote the thread pool, I wasn’t calling Sleep(), and all my threads became processor hogs. If you downloaded the code or are writing it yourself, go ahead and comment out the Sleep() 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 this multithreaded goodness…

>>Next, Step 3: Enter Thread Pool >>

One thought on “4 – Thread Pool Step 2: Setting Up the Threads

  1. JaxSkenteeken November 28, 2013 / 12:29 AM

    Вам платят за то, что Вы посещаете рекламные страницы, регистрируетесь на сайтах, читаете письма, смотрите видео. В этом нет ничего сложного просто присоединяйтесь к нам прямо сейчас и вы начнете получать стабильный доход ежедневно.

    Ссылка на сайт:
    http://tinyurl.com/k5frt5v

    Удачи Вам!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s