创建线程

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(LPTSTR 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(LPTSTR 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 函数。

如果创建线程在新线程之前退出,则传递局部变量的地址是有风险的,因为指针变得无效。 而是传递指针以动态分配的内存,或使创建线程等待新线程终止。 还可以使用全局变量将数据从创建线程传递到新线程。 使用全局变量时,通常需要同步多个线程的访问。 有关同步的详细信息,请参阅 同步多个线程的执行

创建线程可以使用参数 CreateThread 来指定以下内容:

  • 新线程句柄的安全属性。 这些安全属性包括一个继承标志,用于确定句柄是否可以由子进程继承。 安全属性还包括一个安全描述符,系统用于在授予访问权限之前对线程句柄的所有后续使用执行访问检查。
  • 新线程的初始堆栈大小。 线程的堆栈在进程的内存空间中自动分配;系统根据需要增加堆栈,并在线程终止时释放堆栈。 有关详细信息,请参阅 线程堆栈大小
  • 一个创建标志,用于创建处于挂起状态的线程。 挂起后,在调用 ResumeThread 函数之前,线程不会运行。

还可以通过调用 CreateRemoteThread 函数来创建线程。 调试器进程使用此函数来创建在所调试进程的地址空间中运行的线程。

终止线程