使用 CreateThread() API 创建线程






4.22/5 (47投票s)
2006年3月24日
10分钟阅读

440913

7689
使用 CreateThread() API 创建线程。
引言
本文档旨在帮助初学者初步学习创建线程。本文档将解释如何使用 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_1
、Thread_no_2
和 Thread_no_3
。每个线程由一个函数表示。所以我们来命名这些函数。对应 Thread_no_1
的函数名为 Thread_no_1()
。对应 Thread_no_2
的函数名为 Thread_no_2()
。对应 Thread_no_3
的函数名为 Thread_no_3()
。因此,我们有三个函数,每个函数代表一个特定的线程。因此,当我们说三个线程并发运行时,意味着三个函数正在并发执行。换句话说,当我们说三个线程 Thread_no_1
、Thread_no_2
和 Thread_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
的形式接受馈送给它的数据。它定义了两个整数变量 Data
和 count
。它定义了一个数据类型为 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
的形式接受馈送给它的数据。它定义了两个整数变量 Data
和 count
。它定义了一个数据类型为 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
的形式接受馈送给它的数据。它定义了两个整数变量 Data
和 count
。它定义了一个数据类型为 HANDLE
的变量 hStdout
。接下来,该函数使用 "GetStdHandle()
" 函数获取屏幕(标准输出)的句柄。如果线程函数未能获得屏幕句柄,它将返回。接下来,该函数从 lpParam
中提取数据并将其存储在变量 Data
中。这些数据是通过 main()
调用 CreateThread()
函数传递给 Thread_no_3()
的。接下来,该函数实现一个 for
循环,该循环执行 10 次。DisplayMessage()
函数在 for
循环内被调用。因此,DisplayMessage
函数执行十次。它的工作是显示一个字符串,指示正在执行的线程、迭代次数以及传递给线程的数据。
现在,我们来看 DisplayMessage()
函数。
DisplayMessage()
此函数不返回任何值。此函数接受四个参数。第一个参数是屏幕句柄,第二个参数是线程名称,第三个参数是线程数据,最后一个参数是线程正在执行的迭代次数。此函数声明一个缓冲区 msgbuff
来保存要在屏幕上显示的该消息。此函数声明 cchStringSize
来保存要显示的该消息的长度。此函数声明 dwChars
作为 DWORD
。WriteConsole
函数需要它。StringCchPrintf()
函数将要显示的该消息复制到 msgbuff
中。StringCchLength()
函数计算要显示的该消息的长度。WriteConsole()
函数显示该消息。显示该消息后,它将暂停一秒钟。
重申一下,我们程序中每个线程的工作是每秒显示一条消息。
接下来是 main()
程序。
主程序
主程序的目标是创建三个线程,让它们并发运行直到终止。线程使用 CreateThread()
函数创建。当 CreateThread()
函数创建线程时,它会返回一个线程句柄。我们的主程序必须存储这个句柄。由于我们创建了三个线程,我们的程序需要存储三个线程句柄,因此我们的程序定义了三个句柄变量,即 Handle_Of_Thread_1
、Handle_Of_Thread_2
和 Handle_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日:初次发布。