创建线程

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

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

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

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

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

终止线程