CreateThread Function Guide: How to Create and Manage Threads in Windows API Programming

Written by Yannick Brun

November 20, 2025

Quick Answer: Creating Windows Threads with CreateThread

The CreateThread function creates a new execution thread in your Windows application. Here’s the essential syntax you need:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

Key point: Use _beginthreadex instead of CreateThread if your application uses the C Runtime Library (CRT). CreateThread is fine for pure Win32 applications without CRT dependencies.

Understanding CreateThread vs _beginthreadex

Before diving into implementation, you need to choose the right thread creation function:

Function When to Use Key Benefits
CreateThread Pure Win32 API applications Direct OS control, minimal overhead
_beginthreadex Applications using CRT functions Thread-safe CRT initialization

Complete CreateThread Syntax Breakdown

Let’s examine each parameter with practical context:

๐Ÿ“‹ Required Headers

#include <Windows.h>
#include <processthreadsapi.h>  // Automatically included with Windows.h

Parameter Details

  • lpThreadAttributes: Security descriptor (usually NULL for default)
  • dwStackSize: Stack size in bytes (0 = use default 1MB)
  • lpStartAddress: Your thread function pointer
  • lpParameter: Data passed to thread function (can be NULL)
  • dwCreationFlags: Thread creation behavior (0 = start immediately)
  • lpThreadId: Receives thread ID (can be NULL)

Your First Working Thread Example

Here’s a complete, ready-to-compile example:

#include <Windows.h>
#include <iostream>

// Thread function signature must match LPTHREAD_START_ROUTINE
DWORD WINAPI WorkerThread(LPVOID lpParameter) {
    int* data = (int*)lpParameter;
    
    for (int i = 0; i < 5; i++) {
        std::cout << "Thread working... " << *data << std::endl;
        Sleep(1000);  // Simulate work
    }
    
    return 0;  // Thread exit code
}

int main() {
    int threadData = 42;
    DWORD threadId;
    
    // Create the thread
    HANDLE hThread = CreateThread(
        NULL,           // Default security attributes
        0,              // Default stack size
        WorkerThread,   // Thread function
        &threadData,    // Parameter to thread function
        0,              // Start immediately
        &threadId       // Store thread ID
    );
    
    if (hThread == NULL) {
        std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
        return 1;
    }
    
    std::cout << "Thread created with ID: " << threadId << std::endl;
    
    // Wait for thread to complete
    WaitForSingleObject(hThread, INFINITE);
    
    // Clean up
    CloseHandle(hThread);
    
    return 0;
}

Essential Thread Management Patterns

๐Ÿ”„ Multiple Thread Creation

When creating multiple threads, proper handle management becomes crucial:

const int THREAD_COUNT = 4;
HANDLE threads[THREAD_COUNT];
DWORD threadIds[THREAD_COUNT];

for (int i = 0; i < THREAD_COUNT; i++) {
    threads[i] = CreateThread(NULL, 0, WorkerThread, &i, 0, &threadIds[i]);
    if (threads[i] == NULL) {
        // Handle error
        break;
    }
}

// Wait for all threads to complete
WaitForMultipleObjects(THREAD_COUNT, threads, TRUE, INFINITE);

// Clean up all handles
for (int i = 0; i < THREAD_COUNT; i++) {
    if (threads[i] != NULL) {
        CloseHandle(threads[i]);
    }
}

๐Ÿ’พ Safe Data Passing Techniques

Avoid race conditions when passing data to threads:

โš ๏ธ Common Mistake – Loop Variable Sharing

Never pass a loop variable directly to multiple threads – they’ll all reference the same memory location!

// โŒ WRONG - All threads share same variable
for (int i = 0; i < 4; i++) {
    CreateThread(NULL, 0, WorkerThread, &i, 0, NULL);  // Race condition!
}

// โœ… CORRECT - Each thread gets unique data
struct ThreadData {
    int id;
    char* message;
};

ThreadData data[4];
for (int i = 0; i < 4; i++) {
    data[i].id = i;
    data[i].message = "Thread data";
    CreateThread(NULL, 0, WorkerThread, &data[i], 0, NULL);
}

Critical Best Practices You Must Follow

๐Ÿงน Handle Cleanup

Always close thread handles – failure to do so creates memory leaks:

๐Ÿ’ก Pro Tip: Immediate Handle Cleanup

If you don’t need to wait for the thread, close the handle immediately after creation. The thread continues running independently.

HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
if (hThread != NULL) {
    CloseHandle(hThread);  // Thread continues running
    // No need to store handle if you won't wait for completion
}

๐Ÿšซ Common Pitfalls to Avoid

Problem Impact Solution
Unclosed handles Memory leaks Always call CloseHandle()
Large stack sizes Limits thread count (~2028 max) Use default size (0) unless needed
Shared data without synchronization Race conditions Use mutexes, critical sections

Advanced Thread Control Options

๐ŸŽ›๏ธ Creation Flags

Control thread behavior with these flags:

  • 0 – Start thread immediately (most common)
  • CREATE_SUSPENDED – Create thread in suspended state
  • STACK_SIZE_PARAM_IS_A_RESERVATION – Treat stack size as reservation
// Create suspended thread for later execution
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 
                             CREATE_SUSPENDED, NULL);

// Do some setup work...

// Start the thread when ready
ResumeThread(hThread);

Debugging and Troubleshooting

๐Ÿ” Error Handling

Always check for thread creation failure:

HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
if (hThread == NULL) {
    DWORD error = GetLastError();
    switch (error) {
        case ERROR_NOT_ENOUGH_MEMORY:
            std::cerr << "Insufficient memory for thread creation" << std::endl;
            break;
        case ERROR_INVALID_PARAMETER:
            std::cerr << "Invalid parameter passed to CreateThread" << std::endl;
            break;
        default:
            std::cerr << "CreateThread failed with error: " << error << std::endl;
    }
    return 1;
}

Modern Alternatives to Consider

While CreateThread remains relevant in 2025, consider these alternatives for new projects:

  • std::thread (C++11+) – Modern C++ standard approach
  • Windows Thread Pool API – Better for many short-lived tasks
  • Task-based patterns – Higher-level abstractions for concurrent operations

Frequently Asked Questions

โ“ Why does CreateThread return NULL?

Common causes include insufficient memory, invalid parameters, or hitting system thread limits. Always check GetLastError() for specific error codes.

โ“ How many threads can I create?

Typically around 2,028 threads per process due to default 1MB stack size and 2GB user-mode address space limit. Reduce stack size to create more threads if needed.

โ“ Should I use CreateThread or _beginthreadex?

Use _beginthreadex if your application uses any C Runtime Library functions (printf, malloc, etc.). Use CreateThread for pure Win32 applications.

โ“ What happens if I don’t close the thread handle?

The handle remains in memory until your process terminates, causing a resource leak. The thread itself continues running normally.

โ“ Can I pass multiple parameters to a thread?

Pass a pointer to a structure containing all your parameters. This is safer and more maintainable than trying to pack multiple values into a single pointer.

โ“ How do I know when a thread has finished?

Use WaitForSingleObject() with the thread handle, or check the thread’s exit code with GetExitCodeThread().

Related Resources:

Hi, Iโ€™m Yannick Brun, the creator of ListPoint.co.uk.
Iโ€™m a software developer passionate about building smart, reliable, and efficient digital solutions. For me, coding is not just a job โ€” itโ€™s a craft that blends creativity, logic, and problem-solving.

Leave a Comment