概念
在现代计算机中,多线程编程是一种强大的并发执行计数,允许多个线程在单个程序内部并行执行,提高程序的执行效率和响应速度。
线程,作为CPU调度的最小单元,它被用来执行程序中的指令。一个线程是进程中的一个单一顺序控制流程,多线程则允许一个进程内有多个这样的控制流程同时运行。
多线程可以看做一种特殊的多任务处理方式,只不过这些“任务”是在同一个进程的上下文中并发运行的。这使得线程间可以共享进程资源,如内存数据、文件资源,而不需要额外的进程间通信机制。
程序本身是静态的,存储在某种媒介上,比如硬盘或光盘。当操作系统运行程序时,会创建一个进程,该进程会将程序的代码和其数据加载到内存中,以供CPU执行。
CPU
CPU(Central Processing Unit,中央处理器)是计算机的核心硬件之一,负责解释和执行大部分的命令。它从基本构造上包括算数逻辑单元、控制单元、寄存器、缓存和总线。
CPU组成部分介绍
- 算数逻辑单元(ALU)
ALU是CPU的计算核心,负责执行所有的算数运算(如加减乘除)和逻辑运算(如比较大小、与、或、非等)。ALU根据控制单元发来的指令进行相应的数据处理。
- 控制单元(CU)
控制单元负责从内存中取出指令,解析并执行它们。它控制CPU内部和CPU与其他计算机组件之间的数据流动,确保各个部件协调一致。
- 寄存器
寄存器是CPU内部非常小但速度极快的存储设备。它们用于暂存指令、数据和地址等信息。寄存器的使用极大地提高了CPU的处理速度和效率。
- 缓存
缓存是位于CPU和主内存之间的小容量高速存储区域,用于暂存CPU经常访问的数据和指令。通过缓存,CPU可以快速获取需要的信息,而不必每次都去较慢的内存中读取,从而加速处理过程。
- 总线
总线是连接CPU内部各部分以及CPU与其他计算机组件的通道。包括数据总线、地址总线和控制总线,分别用于数据传输、地址传输和控制信号传输。
CPU处理流程
以前CPU都是单核的,同一时间只能处理单个指令;而现代的CPU都是多核的,同一时间能够处理多个指令。
单核CPU下处理多任务的流程:
双核CPU下处理多任务的流程:
线程
进程作为系统分配资源的最小单元,由至少一个线程组成,由多个线程组成的叫多线程程序。
线程和进程最大的区别是进程之间不共享内存。
为什么要使用多线程?
在Windows操作系统下运行计算器程序,这个程序有以下任务需要处理:
- UI渲染(重绘事件)
- 响应鼠标事件
- 响应按键事件
- 数据计算
如果这个程序是一个单线程程序,那么它的具体流程如下:
传统的GUI程序通常不需要开发者显式创建单独的线程来等待和响应鼠标和键盘事件,通常由UI线程自动处理。
UI线程的主体就是一个事件循环,该循环就是处理事件队列中的事件。事件队列维护了所有和UI相关的事件,比如用户输入事件(鼠标事件、键盘事件)、界面控制事件(窗口事件(调整大小事件、移动窗口事件、关闭窗口事件等)、控件事件(按钮点击事件、文本框输入事件等))等等。
从上图中可以看到一个线程就是一直在处理一个循环,在这个循环中处理各种事件。其中,鼠标事件和键盘事件都可能触发数据计算的任务,数据计算成功后,再响应重绘事件,将结果更新到UI上。
这样会有什么问题呢?
假设数据计算这个任务耗时2秒,那么整个循环中的其他事件就只能在2秒后才能响应了。在这2秒时间内,UI无法重绘导致“界面冻结”,用户无法输入数据。
怎么解决呢?
在传统的GUI程序开发中,我们往往会为一些耗时的数据处理创建新的线程,所有耗时的任务都在这个新的线程中执行。
在C++中创建线程
以前的C++标准库并没有线程接口,都是由各自的平台自己实现的。比如在Windows下是通过CreateThread
来创建线程的,在Linux下是通过pthread_create
来创建线程的。
从C++11开始,标准库正式引入了线程std::thread
,下面我们通过标准库的std::thread
来写一个多线程程序:
#include <iostream>
#include <thread>
using std::thread;
void thd1_fun() {
for (int i = 0; i < 10; ++i) {
std::cout << "线程1正在运行!\n";
}
}
void thd2_fun() {
for (int i = 0; i < 10; ++i) {
std::cout << "线程2正在运行!\n";
}
}
void thd3_fun() {
for (int i = 0; i < 10; ++i) {
std::cout << "线程3正在运行!\n";
}
}
int main() {
thread t1(thd1_fun);
thread t2(thd2_fun);
thread t3(thd3_fun);
t1.join();
t2.join();
t3.join();
return 0;
}
运行结果如下:
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程1正在运行!
线程3正在运行!
线程3正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程2正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
线程3正在运行!
因为三个函数在不同的线程中运行,所以每次运行结果中的输出顺序是可能不一样的。
后面我们再详细介绍C++多线程编程,未完待续… …