一、多线程
在学习多线程之前,我们先了解一下什么是任务。
任务:顾名思义,任务就是需要完成的活动,或者说是实现某个目的的一系列操作。
举个简单的例子:在我们日常生活中吃饭就是一项任务,玩手机也是一项任务。
那什么是多任务呢?多任务就是多个简单任务。同样举个简单的例子,我们边吃饭边玩手机。那吃饭和玩手机两个简单任务合在一起,我们就称之为多任务。
在了解完任务以后,我们再来了解一下什么是线程。
线程:在操作系统中,线程是进行程序执行和资源分配的最小单位,它是进程的一个实体,是CPU调度和分派的基本单位,也是比进程更小的能独立运行的基本单位。通俗一点的理解也可以说是执行一个任务的过程。
多线程:则是指在单个进程内部可以同时执行多个线程的能力,也就是我们常说的并发执行。
我们同样举个简单的例子,比如在某一时刻,我们去执行吃饭的任务。那么在这一时刻,我们去吃饭的整个活动过程我们都可以把它当做线程。那我们边吃饭边玩手机就是多线程的执行。
注意,这里我们还提到了一个进程的概念,进程就是我们执行程序的一次执行过程。进程与程序还不一样,程序是指令和数据的有序集合,它本身是没有任何运行的含义,是一个静态的概念。而进程则是一个动态的概念。进程还是系统资源分配的单位。
了解到这里,我们就可以明白,进程就是程序的一次执行,所以进程里可以包含多个线程,而每个线程执行一个任务。当然,进程里至少要有一个线程,否则进程的存在就显得毫无意义。
在这里,我们还需要提及一个问题,那就是真假线程的问题。所谓假线程其实就是模拟线程。真正的多线程是指有多个CPU,即多核。像服务器等。而我们模拟出来的多线程,是在一个CPU的情况下,在同一时间点,CPU只能执行一个线程代码,但是CPU在不同的线程之间来回切换执行,切换速度达到微秒级,所以在我们看来就是并发执行的。比如我们边吃饭边玩手机,看似两个任务是同时进行,但事实上,在某一时刻我们只是在执行吃饭任务或玩手机任务,只是我们的大脑在两个任务之间来回执行的速度过快,导致我们以为我们是在同时执行。然而,在大多数编程语言中,如Java、Python、C++和C#等,我们可以在程序中创建线程,这些线程是由操作系统进行管理和调度的。这些线程是真正的操作系统线程,而不是模拟线程(想深入了解的可以学习操作系统相关知识),所以,通过使用多线程编程技术,我们可以充分利用多核处理器和多处理器的计算能力,提高程序的执行效率和响应能力。
二、C++的多线程实现
在使用C++创建多线程程序时,我们可以引用Windows API函数来创建和管理线程。所以我们需要先引用windows.h头文件。
#include <windows.h>
然后,我们可以声明两个句柄变量和两个DWORD类型变量,用于存储线程的标识符和ID(这里我们以C++创建两个线程为例,所以只声明两个线程的标识符和ID)。
HANDLE threadHandle1, threadHandle2;
DWORD threadId1, threadId2;
紧接着,我们可以使用CreateThread函数在Windows操作系统中创建一个新的线程,并在新线程上运行,以下是CreateThread函数在Windows API中的声明:
HANDLE CreateThread
(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
接下来是对CreateThread函数中的参数的说明:
参数 | 含义 |
lpThreadAttributes | 指向一个SECURITY_ATTRIBUTES结构的指针,该结构用于确定返回的线程句柄是否可以被子进程继承。如果此参数为NULL,则线程句柄不能被继承。 |
dwStackSize | 指定线程的初始堆栈大小。如果此参数为0,则使用与调用线程相同的堆栈大小。 |
lpStartAddress | 指向线程函数的指针。当线程启动时,它从这里开始执行。 |
lpParameter | 指向传递给线程函数的参数的指针。 |
dwCreationFlags | 指定线程的启动和运行方式。 |
lpThreadId | 如果此参数不为NULL,则返回新线程的线程ID。 |
对CreateThread函数了解完以后,我们可以通过以下程序创建两个线程运行,并对线程是否创建成功做出判断:
// 创建第一个线程
threadHandle1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &threadId1);
if (threadHandle1 == NULL) {
std::cout << "Failed to create thread 1." << std::endl;
return 1;
}
// 创建第二个线程
threadHandle2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &threadId2);
if (threadHandle2 == NULL) {
std::cout << "Failed to create thread 2." << std::endl;
CloseHandle(threadHandle1); // 关闭第一个线程的句柄
return 1;
}
两个线程创建以后,我们还要创建相应的线程执行函数,如下:
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
// 线程一执行的代码
std::cout << "Thread1 is running." << std::endl;
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
// 线程二执行的代码
std::cout << "Thread2 is running." << std::endl;
return 0;
}
其中DWORD WINAPI是线程函数的原型。ThreadProc1和ThreadProc2是线程函数名。LPVOID lpParameter是线程函数的参数。
在线程和线程函数创建运行以后,我们可以通过WaitForSingleObject函数来实现线程的执行等待,以下是WaitForSingleObject函数的声明:
DWORD WaitForSingleObject
(
HANDLE hHandle,
DWORD dwMilliseconds
);
参数说明:
参数 | 含义 |
hHandle | 指定要等待的对象的句柄。这可以是线程的句柄、事件、互斥量等。 |
dwMilliseconds | 指定等待的时间,以毫秒为单位。如果此参数为INFINITE,则函数会无限期地等待,直到对象变为信号状态。 |
在对 WaitForSingleObject了解完以后,我们可以使用该函数同步并等待线程运行结束。
// 等待两个线程执行完成
WaitForSingleObject(threadHandle1, INFINITE);
WaitForSingleObject(threadHandle2, INFINITE);
最后当一个线程执行完以后,我们要使用 CloseHandle函数关闭已打开的线程句柄,然后进行资源释放。注意,如果不使用CloseHandle函数释放资源,在做大型项目开发时,可能会导致应用程序出现性能问题和稳定性问题。
以下是 CloseHandle函数的声明,我们只需要在括号里写上句柄变量名即可。
BOOL CloseHandle(
HANDLE hObject
);
下面是完整的C++创建线程程序:
#include <iostream>
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
// 线程执行的代码
std::cout << "Thread1 is running." << std::endl;
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
// 线程执行的代码
std::cout << "Thread2 is running." << std::endl;
return 0;
}
int main() {
HANDLE threadHandle1, threadHandle2;
DWORD threadId1, threadId2;
// 创建第一个线程
threadHandle1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &threadId1);
if (threadHandle1 == NULL) {
std::cout << "Failed to create thread 1." << std::endl;
return 1;
}
// 创建第二个线程
threadHandle2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &threadId2);
if (threadHandle2 == NULL) {
std::cout << "Failed to create thread 2." << std::endl;
CloseHandle(threadHandle1); // 关闭第一个线程的句柄
return 1;
}
// 等待两个线程执行完成
WaitForSingleObject(threadHandle1, INFINITE);
WaitForSingleObject(threadHandle2, INFINITE);
// 关闭线程句柄
CloseHandle(threadHandle1);
CloseHandle(threadHandle2);
std::cout << "Both threads have completed execution。" << std::endl;
return 0;
}