Introduction to the Windows Threadpool (part 2)

Download source code from MSDN Code Gallery.

In the previous posts, I talked about using the windowsthreadpool::SimpleThreadPool class to queue work items to the process-wide threadpool and queuing work items with different priorities.  In this post, I will show how to create your own private threadpool using the class windowsthreadpool::PrivateThreadPool.

Private Threadpool

The private threadpool allows a developer to have their own pool and control work items that are queued to the threadpool. In addition, having a private threadpool also allows the developer to control the maximum and minimum of threads the threadpool can create.

 

To create a private threadpool, you need to call CreateThreadpool which returns a handle to the pool object, which is done in the constructor of the class PrivateThreadPool. The private pool is closed in the destructor by calling CloseThreadpool.

PrivateThreadPool(): InfraInitialized(false)

{

       Pool = CreateThreadpool(NULL);

       if (!Pool)

       {

              throw "Error: CreateThreadpool Failed.";

}

 

~PrivateThreadPool()

{

       CloseThreadpool(Pool);

}

You queue work items to the private threadpool using the same methods (QueueUserWorkItem) as the simple threadpool class and wait for the work items to finish using the same WaitForAll. Because we are using a private threadpool, we need to associate our callback environment block with our private threadpool, so that all work items submitted using this environment block are submitted to the private threadpool. This is done in the InitializeInfra method by calling the function SetThreadpoolCallbackPool.

void InitializeInfra()

{

       if (!InfraInitialized)

       {

              InitializeThreadpoolEnvironment(&CallbackEnvironment);

              SetThreadpoolCallbackPool(&CallbackEnvironment, Pool);

              CleanupGroup = CreateThreadpoolCleanupGroup();

              SetThreadpoolCallbackCleanupGroup(&CallbackEnvironment, CleanupGroup, NULL);            

              InfraInitialized = true;

Submitting work items to a private threadpool requires using function SubmitThreadpoolWork. This function takes in a pointer to a TP_WORK object which is created with the CreateThreadpoolWork function. The work is submitted using an internal helper class WorkCallBack which is similar to the helper class SimpleThreadPool uses except in this it uses SubmitThreadpoolwork to submit work items. The WorkCallBack instance is destroyed in the callback method after the user callback runs. 

// Internal class which submits the work items

template <class Function>

class WorkCallBack

{

private:

       const Function m_Func;

       PVOID state;

       PTP_WORK work;

 

       static void CALLBACK callback (PTP_CALLBACK_INSTANCE Instance, PVOID Param, PTP_WORK work)

       {

              UNREFERENCED_PARAMETER(Instance);

              UNREFERENCED_PARAMETER(work);

                    

              WorkCallBack<Function> *pc = reinterpret_cast<WorkCallBack<Function>*>(Param);

              pc->m_Func(pc->state);                  

              delete pc;

 

              return;

       }

 

public:

       WorkCallBack(const Function Func, PVOID st, PTP_CALLBACK_ENVIRON pEnv) : m_Func(Func), state(st)

       {                   

              work = CreateThreadpoolWork(callback, this, pEnv);

              if (!work)

              {

                     throw "Error: Could not create threadpool work.";

              }

                    

              SubmitThreadpoolWork(work);

       }

The private threadpool supports two operations that are not supported by the default process wide threadpool, setting the maximum and minimum number of threads that the threadpool can create. It should be pretty obvious why these operations are not supported on the default process-wide threadpool; the process-wide threadpool is also used by the system to execute its work items, so you don’t want the developer to set arbitrary limits on this threadpool and accidentally impact system performance. If you are creating your own private threadpool, you shouldn’t have to set the maximum and minimum number of threads; the system is capable of managing thread lifetimes on its own and unless you really notice some problem don’t have to change these settings.

 

As mentioned in the previous post, if you have low priority work items, instead of modifying the thread’s priority in the callback, you should use a private threadpool. In the example below I am creating a background threadpool which can have a maximum of only one thread. So at any given time only one work item can execute. And if there are no more work items to execute the threadpool will kill that thread as well as determined by its internal heuristics.

Example Code:

void CALLBACK PrintI(PVOID state)

{

       int *i = reinterpret_cast<int *> (state);

       cout << *i << " ," << endl;

}

 

int _cdecl _tmain()

{

       using namespace windowsthreadpool;

 

// Background threadpool - only one thread running

// work items are executed in FIFO order

//

PrivateThreadPool backgroundworker;

backgroundworker.SetThreadpoolMax(1);

      

for(int i=0; i<10; ++i)

{

              backgroundworker.QueueUserWorkItemWithLowPri(PrintI, &arr[i]);            

}

 

backgroundworker.WaitForAll();

And that’s it, your own private threadpool. As the private threadpool only has one thread, each work item executes serially, so technically we don’t need to queue with low priority, the same behavior can be obtained by just queuing normally.

 

Since this is a private threadpool to run background work items, if you really want you could even modify the thread priority in the work item. However, note that if you do modify the thread priority in the work item, the priority needs to be reset back to the original value before the work item returns.

 

Next up; creating a timer class using the win32 threadpool functions...