65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 CreateThread() API 创建线程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (47投票s)

2006年3月24日

10分钟阅读

viewsIcon

440913

downloadIcon

7689

使用 CreateThread() API 创建线程。

Sample Image

引言

本文档旨在帮助初学者初步学习创建线程。本文档将解释如何使用 CreateThread() 函数创建线程。当我开始学习多线程编程时,我花了很长时间才找到简单易懂的程序和文章,能够用简单的英语演示和解释与多线程相关的概念。这也是我写第一篇关于多线程的文章并在 CodeProject 上发表的动力。

背景

我将展示一个程序,该程序将演示使用 Windows API CreateThread() 创建和并发执行三个线程。

使用代码

我已将源代码打包在 zip 文件 source.zip 中。我已将可执行文件 "Test1.exe" 打包在 Test1.zip 中。当您从 Windows 命令提示符运行 Test1.exe 时,您将在 DOS 框中看到输出,如下图所示。

代码

#include <windows.h> <WINDOWS.H>
#include <strsafe.h> <STRSAFE.H>
#include <stdio.h><STDIO.H>

#define BUF_SIZE 255

//------------------------------------------
// A function to Display the message
// indicating in which tread we are
//------------------------------------------
void DisplayMessage (HANDLE hScreen, 
     char *ThreadName, int Data, int Count)
{

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;

    // Print message using thread-safe functions.
    StringCchPrintf(msgBuf, BUF_SIZE, 
       TEXT("Executing iteration %02d of %s" 
       " having data = %02d \n"), 
       Count, ThreadName, Data); 
    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
    WriteConsole(hScreen, msgBuf, cchStringSize, 
                 &dwChars, NULL);
    Sleep(1000);
}

//-------------------------------------------
// A function that represents Thread number 1
//-------------------------------------------
DWORD WINAPI Thread_no_1( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen.
    // Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 4; count++ )
    {
       DisplayMessage (hStdout, "Thread_no_1", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 2
//-------------------------------------------
DWORD WINAPI Thread_no_2( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) == 
         INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 7; count++ )
    {
      DisplayMessage (hStdout, "Thread_no_2", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 3
//-------------------------------------------
DWORD WINAPI Thread_no_3( LPVOID lpParam ) 
{
    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;

    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 10; count++ )
    {
     DisplayMessage (hStdout, "Thread_no_3", Data, count);
    }
    
    return 0; 
} 

 
void main()
{
    // Data of Thread 1
    int Data_Of_Thread_1 = 1;
    // Data of Thread 2
    int Data_Of_Thread_2 = 2;
    // Data of Thread 3
    int Data_Of_Thread_3 = 3;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_1 = 0;
    // variable to hold handle of Thread 1 
    HANDLE Handle_Of_Thread_2 = 0;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_3 = 0;
    // Aray to store thread handles 
    HANDLE Array_Of_Thread_Handles[3];

    // Create thread 1.
    Handle_Of_Thread_1 = CreateThread( NULL, 0, 
           Thread_no_1, &Data_Of_Thread_1, 0, NULL);  
    if ( Handle_Of_Thread_1 == NULL)
        ExitProcess(Data_Of_Thread_1);
    
    // Create thread 2.
    Handle_Of_Thread_2 = CreateThread( NULL, 0, 
           Thread_no_2, &Data_Of_Thread_2, 0, NULL);  
    if ( Handle_Of_Thread_2 == NULL)
        ExitProcess(Data_Of_Thread_2);
    
    // Create thread 3.
    Handle_Of_Thread_3 = CreateThread( NULL, 0, 
           Thread_no_3, &Data_Of_Thread_3, 0, NULL);  
    if ( Handle_Of_Thread_3 == NULL)
        ExitProcess(Data_Of_Thread_3);


    // Store Thread handles in Array of Thread
    // Handles as per the requirement
    // of WaitForMultipleObjects() 
    Array_Of_Thread_Handles[0] = Handle_Of_Thread_1;
    Array_Of_Thread_Handles[1] = Handle_Of_Thread_2;
    Array_Of_Thread_Handles[2] = Handle_Of_Thread_3;
    
    // Wait until all threads have terminated.
    WaitForMultipleObjects( 3, 
        Array_Of_Thread_Handles, TRUE, INFINITE);

    printf("Since All threads executed" 
           " lets close their handles \n");

    // Close all thread handles upon completion.
    CloseHandle(Handle_Of_Thread_1);
    CloseHandle(Handle_Of_Thread_2);
    CloseHandle(Handle_Of_Thread_3);
}

代码解释

我们的目标是使用 Windows API CreateThread() 创建三个线程并并发执行它们。假设这三个线程分别是 Thread_no_1Thread_no_2Thread_no_3。每个线程由一个函数表示。所以我们来命名这些函数。对应 Thread_no_1 的函数名为 Thread_no_1()。对应 Thread_no_2 的函数名为 Thread_no_2()。对应 Thread_no_3 的函数名为 Thread_no_3()。因此,我们有三个函数,每个函数代表一个特定的线程。因此,当我们说三个线程并发运行时,意味着三个函数正在并发执行。换句话说,当我们说三个线程 Thread_no_1Thread_no_2Thread_no_3 并发运行时,意味着三个函数 Thread_no_1()Thread_no_2()Thread_no_3() 正在并发执行。因此,我们在程序中定义了三个函数。这三个函数是:

  • Thread_no_1()
  • Thread_no_2()
  • Thread_no_3()

每个线程处理一部分数据,这些数据被馈送给它,或者处理全局可用的数据。在我们的例子中,我们不使用任何全局数据。所以我们示例中的线程将使用被馈送给它们的数据。并且程序中的线程由一个函数表示。所以向线程馈送数据意味着向函数馈送数据。我们如何向函数馈送数据?通过将参数传递给函数。因此,我们代表线程的函数通过参数接受数据。所以现在,代表线程的每个函数看起来都像这样……

  • Thread_no_1(LPVOID lpParam)
  • Thread_no_2(LPVOID lpParam)
  • Thread_no_3(LPVOID lpParam)

其中 "LPVOID lpParam" 是指向 void 的长指针。

谁来向这些线程(即线程函数)传递数据?嗯……这是由 Windows API CreateThread() 完成的。稍后将讨论如何做到这一点。想知道谁调用 CreateThread() API 吗?是 main() 程序调用 CreateThread() API 来简单地创建一个线程。程序的执行从哪里开始?它从 main() 开始。因此,main() 调用 CreateThread()CreateThread() 函数创建线程。线程并发执行并终止。这就是故事!

现在,让我们理解线程的实现,即线程函数。考虑第一个线程函数 Thread_no_1

Thread_no_1()

该函数的原型是 "DWORD WINAPI Thread_no_1( LPVOID lpParam )"。对此请勿提问!它必须是这样的。让我们在初学者级别接受它。该函数以长 void 指针变量 lpParam 的形式接受馈送给它的数据。它定义了两个整数变量 Datacount。它定义了一个数据类型为 HANDLE 的变量 hStdout。接下来,该函数使用 "GetStdHandle()" 函数获取屏幕(标准输出)的句柄。如果线程函数未能获得屏幕句柄,它将返回。接下来,该函数从 lpParam 中提取数据并将其存储在变量 Data 中。这些数据是通过 main() 调用 CreateThread() 函数传递给 Thread_no_1() 的。接下来,该函数实现一个 for 循环,该循环执行四次。DisplayMessage() 函数在 for 循环内被调用。因此,DisplayMessage 函数执行四次。它的工作是显示一个字符串,指示正在执行的线程、迭代次数以及传递给线程的数据。

Thread_no_2()

该函数的原型是 "DWORD WINAPI Thread_no_2( LPVOID lpParam )"。对此请勿提问!它必须是这样的。让我们在初学者级别接受它。该函数以长 void 指针变量 lpParam 的形式接受馈送给它的数据。它定义了两个整数变量 Datacount。它定义了一个数据类型为 HANDLE 的变量 hStdout。接下来,该函数使用 "GetStdHandle()" 函数获取屏幕(标准输出)的句柄。如果线程函数未能获得屏幕句柄,它将返回。接下来,该函数从 lpParam 中提取数据并将其存储在变量 Data 中。这些数据是通过 main() 调用 CreateThread() 函数传递给 Thread_no_2() 的。接下来,该函数实现一个 for 循环,该循环执行七次。DisplayMessage() 函数在 for 循环内被调用。因此,DisplayMessage 函数执行七次。它的工作是显示一个字符串,指示正在执行的线程、迭代次数以及传递给线程的数据。

Thread_no_3()

该函数的原型是 "DWORD WINAPI Thread_no_3( LPVOID lpParam )"。对此请勿提问!它必须是这样的。让我们在初学者级别接受它。该函数以长 void 指针变量 lpParam 的形式接受馈送给它的数据。它定义了两个整数变量 Datacount。它定义了一个数据类型为 HANDLE 的变量 hStdout。接下来,该函数使用 "GetStdHandle()" 函数获取屏幕(标准输出)的句柄。如果线程函数未能获得屏幕句柄,它将返回。接下来,该函数从 lpParam 中提取数据并将其存储在变量 Data 中。这些数据是通过 main() 调用 CreateThread() 函数传递给 Thread_no_3() 的。接下来,该函数实现一个 for 循环,该循环执行 10 次。DisplayMessage() 函数在 for 循环内被调用。因此,DisplayMessage 函数执行十次。它的工作是显示一个字符串,指示正在执行的线程、迭代次数以及传递给线程的数据。

现在,我们来看 DisplayMessage() 函数。

DisplayMessage()

此函数不返回任何值。此函数接受四个参数。第一个参数是屏幕句柄,第二个参数是线程名称,第三个参数是线程数据,最后一个参数是线程正在执行的迭代次数。此函数声明一个缓冲区 msgbuff 来保存要在屏幕上显示的该消息。此函数声明 cchStringSize 来保存要显示的该消息的长度。此函数声明 dwChars 作为 DWORDWriteConsole 函数需要它。StringCchPrintf() 函数将要显示的该消息复制到 msgbuff 中。StringCchLength() 函数计算要显示的该消息的长度。WriteConsole() 函数显示该消息。显示该消息后,它将暂停一秒钟。

重申一下,我们程序中每个线程的工作是每秒显示一条消息。

接下来是 main() 程序。

主程序

主程序的目标是创建三个线程,让它们并发运行直到终止。线程使用 CreateThread() 函数创建。当 CreateThread() 函数创建线程时,它会返回一个线程句柄。我们的主程序必须存储这个句柄。由于我们创建了三个线程,我们的程序需要存储三个线程句柄,因此我们的程序定义了三个句柄变量,即 Handle_Of_Thread_1Handle_Of_Thread_2Handle_Of_Thread_3。我们的程序还定义了三个数据变量,它们将包含要传递给线程的数据。因此,变量 Data_Of_Thread_1 的内容将被传递给第一个线程,即 Thread_no_1()。变量 Data_Of_Thread_2 的内容将被传递给第二个线程,即 Thread_no_2(),变量 Data_Of_Thread_3 的内容将被传递给第三个线程,即 Thread_no_3。接下来,我们声明一个数组来保存这三个线程句柄。数组的名称是 Array_Of_Thread_Handles[3]。稍后将讨论此数组的目的。

接下来,通过调用 CreateThread() 函数来尝试创建第一个线程。CreateThread 接受六个参数。我们的目标是创建一个简单的线程。所以,我们将重点关注 CreateThread() 函数的第三个和第四个参数。将函数 Thread_no_1() 的地址作为第三个参数传递。将变量 Data_Of_Thread_1 的地址作为第四个参数传递。这就是我们向线程传递数据的方式。CreateThread() 函数创建一个线程,然后线程开始执行。CreateThread() 函数返回 Thread_no_1 的句柄。该句柄被收集在句柄变量 Handle_Of_Thread_1 中。如果返回 NULL 值,程序将以 Data_Of_Thread_1 的退出值为退出。

接下来,通过调用 CreateThread() 函数来尝试创建第二个线程。我们的目标是创建一个简单的线程。将函数 Thread_no_2() 的地址作为第三个参数传递。将变量 Data_Of_Thread_2 的地址作为第四个参数传递。这就是我们向线程传递数据的方式。CreateThread() 函数创建一个线程,然后线程开始执行。CreateThread() 函数返回 Thread_no_2 的句柄。该句柄被收集在句柄变量 Handle_Of_Thread_2 中。如果返回 NULL 值,程序将以 Data_Of_Thread_2 的退出值为退出。

接下来,通过调用 CreateThread() 函数来尝试创建第三个线程。将函数 Thread_no_3() 的地址作为第三个参数传递。将变量 Data_Of_Thread_3 的地址作为第四个参数传递。这就是我们向线程传递数据的方式。CreateThread() 函数创建一个线程,然后线程开始执行。CreateThread() 函数返回 Thread_no_3 的句柄。该句柄被收集在句柄变量 Handle_Of_Thread_3 中。如果返回 NULL 值,程序将以 Data_Of_Thread_3 的退出值为退出。

此时,所有三个线程都在并发执行,您可以在屏幕上看到输出,如上图所示。Thread_no_1 有一个 for 循环,该循环迭代四次。Thread_no_2 有一个 for 循环,该循环迭代七次。Thread_no_3 有一个 for 循环,该循环迭代十次。因此,Thread_no_1 将首先完成,Thread_no_2 将紧随其后完成,而 Thread_no_3 将是最后一个完成的。

WaitForMultipleObjects()

现在所有三个线程都在并发执行,您可以在屏幕上看到输出,如上图所示。我们等待所有线程执行完毕,然后才能退出程序。为此,我们需要调用 WaitForMultipleObjects() 函数。我们必须将所有希望等待的线程的句柄传递给此函数。此函数要求我们将这些句柄存储在数组中,并将此数组作为第二个参数传递给 WaitForMultipleObjects() 函数。因此,我们定义了一个数组 Array_Of_Thread_Handles[3]。我们将这三个线程的句柄存储在此数组中,并将此数组作为第二个参数传递给 WaitForMultipleObjects() 函数。此函数的第一个参数表示我们正在等待三个线程。第三个参数是 TRUE。它表示等待所有线程。最后一个参数传递为 INFINITE。这意味着,等待直到所有线程都完成。

因此,此时,主程序等待所有线程完成其执行。在此期间,线程正在并发运行,正如从上图所示。当所有线程完成后,控制权将转交回主程序。程序打印语句 "所有线程都已执行,让我们关闭它们的句柄",然后继续关闭线程句柄并退出。

希望这个程序能清楚地说明线程的创建。如果您有任何疑问,请告诉我。希望我能回答。

历史

  • 2006年3月19日:初次发布。
© . All rights reserved.