Windows thread pool keeps threads even after being closed

Windows Thread pool maintains threads so that it doesn’t have to create a new one when a work item is submitted. It may surprise you that it maintains threads even after you close the thread pool with CloseThreadpool() API function.

The following code demonstrates it. It starts 100 thread pool work items and monitors the number of threads in the process for the next 70 second. The thread pool object is closed immediately after posting the tasks.

LONG g_activeThreadCount = 0;

void CALLBACK WorkProc(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
{
    InterlockedIncrement(&g_activeThreadCount);
    Sleep(5000);
    InterlockedDecrement(&g_activeThreadCount);
}

void PostThreadpoolWorks(int numberOfThreadpoolWorks)
{
    PTP_POOL pool = CreateThreadpool(NULL);
    if(!pool)
    {
        throw std::runtime_error("Fail to CreateThreadpool.");
    }

    PTP_WORK work = CreateThreadpoolWork(&WorkProc, NULL, NULL);
    if(!work)
    {
        CloseThreadpool(pool);
        throw std::runtime_error("Fail to CreateThreadpoolWork.");
    }

    for(int i = 0; i < numberOfThreadpoolWorks; i++)
    {
        SubmitThreadpoolWork(work);
    }

    CloseThreadpoolWork(work);
    CloseThreadpool(pool);
}

void Test1()
{
    // Post 100 thread pool work. The thread pool object is closed 
    // immediately.
    PostThreadpoolWorks(100);
    
    // keep monitoring the number of threads for 70 seconds.
    DWORD tick = GetTickCount();
    for(int i = 0; i < 100; i++)
    {
        cout << (GetTickCount() - tick) / 1000.0 << " " << GetNumOfThreads() << " " << g_activeThreadCount << endl;
        Sleep(1000);
    }
}

As each work item is finished in 5 seconds, the number of running threads (green) is 100 just for a few seconds. However, the number of threads in the process (red) is maintained for the next 68 seconds.

To confirm the observation, I started threads so that the number of running threads forms the triangle.

It indicates that the process maintains the maximum number of threads used within the last 65 to 70 seconds even though the thread pool object is closed.

Here is the program used in the experiment.

DWORD GetNumOfThreads()
{
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    THREADENTRY32 te32;

    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if(hThreadSnap == INVALID_HANDLE_VALUE)
    {
        return (DWORD)-1;
    }

    te32.dwSize = sizeof(THREADENTRY32);
    
    if(!Thread32First(hThreadSnap, &te32))
    {
        CloseHandle(hThreadSnap);
        return (DWORD)-1;
    }
    
    DWORD count = 0;
    do
    {
        if( te32.th32OwnerProcessID == GetCurrentProcessId() )
        {
            count++;
        }
    } while( Thread32Next(hThreadSnap, &te32 ) );
    
    CloseHandle( hThreadSnap );
    return count;
}

LONG g_activeThreadCount = 0;

void CALLBACK WorkProc(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
{
    InterlockedIncrement(&g_activeThreadCount);
    Sleep(5000);
    InterlockedDecrement(&g_activeThreadCount);
}

void PostThreadpoolWorks(int numberOfThreadpoolWorks)
{
    PTP_POOL pool = CreateThreadpool(NULL);
    if(!pool)
    {
        throw std::runtime_error("Fail to CreateThreadpool.");
    }

    PTP_WORK work = CreateThreadpoolWork(&WorkProc, NULL, NULL);
    if(!work)
    {
        CloseThreadpool(pool);
        throw std::runtime_error("Fail to CreateThreadpoolWork.");
    }

    for(int i = 0; i < numberOfThreadpoolWorks; i++)
    {
        SubmitThreadpoolWork(work);
    }

    CloseThreadpoolWork(work);
    CloseThreadpool(pool);
}

void Test1()
{
    // Post 100 thread pool work. The thread pool object is closed 
    // immediately.
    PostThreadpoolWorks(100);
    
    // keep monitoring the number of threads for 70 seconds.
    DWORD tick = GetTickCount();
    for(int i = 0; i < 100; i++)
    {
        cout << (GetTickCount() - tick) / 1000.0 << " " << GetNumOfThreads() << " " << g_activeThreadCount << endl;
        Sleep(1000);
    }
}

DWORD CALLBACK Test2Proc(LPVOID param)
{
    // Post some thread pool works for every 5 seconds.
    for(int i = 0; i < 10; i++)
    {
        PostThreadpoolWorks(i * 50);
        Sleep(5000);
    }
    for(int i = 10; i >= 0; i--)
    {
        PostThreadpoolWorks(i * 50);
        Sleep(5000);
    }
    return 0;
}

void Test2()
{
    // Start threads to post thread pool works.
    HANDLE hThread = ::CreateThread(NULL, 0, &Test2Proc, 0, 0, NULL);
 
    // keep monitoring the number of threads for 200 seconds.
    DWORD tick = GetTickCount();
    for(int i = 0; i < 200; i++)
    {
        cout << (GetTickCount() - tick) / 1000.0 << " " << GetNumOfThreads() << " " << g_activeThreadCount << endl;
        Sleep(1000);
    }

    CloseHandle(hThread);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Test1();
    Test2();
	return 0;
}
Advertisements

About Moto

Engineer who likes coding
This entry was posted in Advanced Debugging, C++ and tagged , . Bookmark the permalink.

One Response to Windows thread pool keeps threads even after being closed

  1. Renegr says:

    Please fix your code before giving invalid statements! If you create workers which are not children of your created pool, of course they won’t be destroyed if you kill the parent!

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