CreateThread 函数为进程创建新线程。 创建线程必须指定要执行的新线程的代码的起始地址。 通常,起始地址是程序代码中定义的函数的名称(有关详细信息,请参阅 ThreadProc)。 此函数采用单个参数并返回 DWORD 值。 一个进程可以同时执行同一个函数的多个线程。
下面是一个简单的示例,演示如何创建一个执行本地定义的函数 MyThreadFunction的新线程。
调用线程使用 WaitForMultipleObjects 函数持久保存,直到所有工作线程终止为止。 正在等待时调用线程会阻塞;若要继续处理,调用线程将使用 WaitForSingleObject 并等待每个工作线程发出其等待对象的信号。 请注意,如果你在工作线程终止之前关闭其句柄,这不会终止工作线程。 但是,句柄在后续函数调用中不可用。
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#define MAX_THREADS 3
#define BUF_SIZE 255
DWORD WINAPI MyThreadFunction( LPVOID lpParam );
void ErrorHandler(LPCTSTR lpszFunction);
// Sample custom data structure for threads to use.
// This is passed by void pointer so it can be any data type
// that can be passed using a single void pointer (LPVOID).
typedef struct MyData {
int val1;
int val2;
} MYDATA, *PMYDATA;
int _tmain()
{
PMYDATA pDataArray[MAX_THREADS];
DWORD dwThreadIdArray[MAX_THREADS];
HANDLE hThreadArray[MAX_THREADS];
// Create MAX_THREADS worker threads.
for( int i=0; i<MAX_THREADS; i++ )
{
// Allocate memory for thread data.
pDataArray[i] = (PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(MYDATA));
if( pDataArray[i] == NULL )
{
// If the array allocation fails, the system is out of memory
// so there is no point in trying to print an error message.
// Just terminate execution.
ExitProcess(2);
}
// Generate unique data for each thread to work with.
pDataArray[i]->val1 = i;
pDataArray[i]->val2 = i+100;
// Create the thread to begin execution on its own.
hThreadArray[i] = CreateThread(
NULL, // default security attributes
0, // use default stack size
MyThreadFunction, // thread function name
pDataArray[i], // argument to thread function
0, // use default creation flags
&dwThreadIdArray[i]); // returns the thread identifier
// Check the return value for success.
// If CreateThread fails, terminate execution.
// This will automatically clean up threads and memory.
if (hThreadArray[i] == NULL)
{
ErrorHandler(TEXT("CreateThread"));
ExitProcess(3);
}
} // End of main thread creation loop.
// Wait until all threads have terminated.
WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);
// Close all thread handles and free memory allocations.
for(int i=0; i<MAX_THREADS; i++)
{
CloseHandle(hThreadArray[i]);
if(pDataArray[i] != NULL)
{
HeapFree(GetProcessHeap(), 0, pDataArray[i]);
pDataArray[i] = NULL; // Ensure address is not reused.
}
}
return 0;
}
DWORD WINAPI MyThreadFunction( LPVOID lpParam )
{
HANDLE hStdout;
PMYDATA pDataArray;
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
// Make sure there is a console to receive output results.
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
if( hStdout == INVALID_HANDLE_VALUE )
return 1;
// Cast the parameter to the correct data type.
// The pointer is known to be valid because
// it was checked for NULL before the thread was created.
pDataArray = (PMYDATA)lpParam;
// Print the parameter values using thread-safe functions.
StringCchPrintf(msgBuf, BUF_SIZE, TEXT("Parameters = %d, %d\n"),
pDataArray->val1, pDataArray->val2);
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, (DWORD)cchStringSize, &dwChars, NULL);
return 0;
}
void ErrorHandler(LPCTSTR lpszFunction)
{
// Retrieve the system error message for the last-error code.
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message.
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK);
// Free error-handling buffer allocations.
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
该 MyThreadFunction 函数可避免使用 C 运行时库 (CRT),因为其许多函数不是线程安全的,特别是如果不使用多线程 CRT。 如果要在 ThreadProc 函数中使用 CRT,请改用 _beginthreadex 函数。
注释
现代 C++ 替代方案:对于新代码,请考虑使用 std::thread(C++11)或 std::jthread(C++20,支持自动联接以及通过 std::stop_token 实现的协作取消)。 它们提供类型安全的基于 RAII 的线程管理。 如果需要特定于 Win32 的功能(例如安全属性、堆栈大小控制或挂起的线程创建),请使用 CreateThread 。 对于不需要专用线程的异步工作,首选 std::async、Windows 线程池 API 或 C++/WinRT 协程 (co_await)。
如果创建线程在新线程之前退出,则传递本地变量的地址是有风险的,因为指针变得无效。 而是传递指向动态分配内存的指针,或使创建线程等待新线程终止。 还可以使用全局变量将数据从创建线程传递到新线程。 使用全局变量时,通常需要同步多个线程的访问。 有关同步的详细信息,请参阅 同步多个线程的执行。
创建线程可以使用参数 CreateThread 来指定以下内容:
- 新线程句柄的安全属性。 这些安全属性包括一个继承标志,用于确定句柄是否可以由子进程继承。 安全属性还包括一个安全描述符,系统在授予访问权限之前,该描述符用于对线程句柄的所有后续使用执行访问检查。
- 新线程的初始堆栈大小。 线程的堆栈在进程的内存空间中自动分配;系统根据需要增加堆栈,并在线程终止时释放堆栈。 有关详细信息,请参阅 线程堆栈大小。
- 一个创建标志,用于创建处于挂起状态的线程。 挂起时,在调用 ResumeThread 函数之前,线程不会运行。
还可以通过调用 CreateRemoteThread 函数来创建线程。 调试器进程使用此函数来创建在所调试进程的地址空间中运行的线程。
相关主题