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: