C++ 多线程

2025年5月16日 | 阅读 12 分钟

在 C++ 中,多线程是一种强大的技术,它将程序分解为称为线程的小执行单元。多线程允许 CPU 或多核处理器的单个核心同时运行多个线程。使用 C++ 进行编程可以使应用程序将工作分配给能够独立运行或相互通信的自主线程。

Multithreading in C++

在现代计算中,利用多个 CPU 核心对于编写响应迅速、高效且可扩展的应用程序至关重要。C++ 多线程使开发人员能够通过代码的并行执行来实现这一点。

什么是线程?

在 C++ 中,线程是用于特定进程的某种工作单元。在多道程序环境中,可以并发运行多个进程。同样,我们可以使用线程多次执行同一个进程。在这种情况下,每个进程都与一个称为线程的单元相关联。

创建线程

线程类存在于 std::thread 命名空间下。创建该类的实例会启动一个新线程,该线程执行指定的调用任务。

语法

它具有以下语法:

在这个语法中,

  • 线程名:它是 thread 类的对象。
  • 可调用对象:它是可调用的对象,如函数指针或函数对象。

C++ 线程示例

让我们举一个例子来说明 C++ 中的线程。

示例

编译并运行

输出

Thread is running.

说明

在此示例中,thread 是用于创建和管理线程的类。之后,join() 方法确保主线程在继续之前等待创建的线程完成。

使用线程的重要头文件

要使用 C++ 中的多线程,需要几个头文件。其中一些如下:

定义可调用对象

在 C++ 中,可调用对象是指可以由线程执行的任何函数或对象。它包括几个函数,例如函数指针、lambda 表达式以及重载了 operator() 来定义其执行行为的对象。

在 C++ 中,我们可以通过四种方式定义可调用对象。它们如下:

1) 使用函数对象

在线程对象中,我们可以使用函数对象作为可调用对象。如果我们想实现该函数对象,我们需要一个类,并在该类中重载 operator()。创建线程时,包含重载函数的代码将被执行。

语法

它具有以下语法:

使用函数对象的 C++ 线程示例

让我们举一个例子来说明在 C++ 中使用函数对象的线程。

示例

编译并运行

输出

Function Object Thread: Value = 10

说明

在此示例中,我们定义了利用 () 运算符重载的 Functor 类。之后,std::thread 的对象构造函数会自动触发在不同线程上执行 operator() 方法。主线程使用 t.join() 方法等待新线程完成。

2) 使用函数指针

在 C++ 中,函数指针是为 std::thread 定义可调用对象的最简单、最直接的方法。我们可以将非成员函数的地址传递给线程构造函数。

语法

它具有以下语法:

定义函数后,我们必须使用可调用函数创建线程对象。我们可以将参数传递给对象函数名之后的函数。

使用函数指针的 C++ 线程示例

让我们举一个例子来说明在 C++ 中使用函数指针的线程。

示例

编译并运行

输出

Function Pointer Thread: Value = 20

3) 使用 Lambda 表达式

在 C++ 中,lambda 表达式可以直接用作可调用对象来创建 std::thread。Lambda 主要用于快速的、内联的任务,而无需定义单独的函数。

语法

它具有以下语法:

在上面的例子中,主函数必须等待直到线程 t1 完成。通常,线程的 join 函数会阻止其他操作/功能,直到调用线程完成其执行。

使用 Lambda 表达式的 C++ 线程示例

让我们举一个例子来说明在 C++ 中使用 lambda 表达式的线程。

示例

编译并运行

输出

Lambda Thread: Value = 30

说明

在此示例中,我们使用 lambda 创建匿名函数,这些函数直接存在于代码中。std::thread 构造函数需要 lambda 函数及其参数。

4) 使用成员函数

在 C++ 中,我们可以将类成员函数用作 std::thread 的可调用对象。它允许我们创建操作特定对象的线程。

语法

它具有以下语法:

使用成员函数的 C++ 线程示例

让我们举一个例子来说明在 C++ 中使用成员函数的线程。

示例

编译并运行

输出

Car is Running: 1
Car is Running: 2
Car is Running: 3
Car is Running: 4
Car is Running: 5

说明

在此示例中,我们取了成员函数 run() 的 &Car::run 指针。之后,&car 是调用成员函数的对象。6 是传递给 run 函数的参数。最后,t.join() 函数确保主线程等待子线程完成。

可调用线程示例

我们在下面的程序中提供了一个完整的代码示例,用于创建和执行线程。

示例

编译并运行

输出

Thread 1 :: callable => function pointer
Thread 3 :: callable => lambda expression
Thread 3 :: callable => lambda expression
Thread 2 :: callable => function object
Thread 2 :: callable => function object
Thread 1 :: callable => function pointer

说明

在此示例中,我们使用三种不同的可调用对象(函数指针、对象和 lambda 表达式)创建了三个线程。我们创建了每个线程的两个实例并启动它们。如输出所示,三个线程同时独立地运行。

C++ 中的线程管理

在 C++ 中,线程管理涉及控制和协调由 std::thread 类创建过程产生的线程。线程管理需要有效的执行监督,以及适当的同步方法和终止程序,以及正确清理资源以防止数据竞争、内存泄漏和未定义行为。

定义了几个函数来管理线程。它们可以重用于执行多项任务。

函数描述
join()它等待线程完成执行。
detach()它用于分离线程以独立运行(类似于守护进程)。
joinable()检查线程是否可以被 join 或 detach。
get_id()它返回线程的唯一标识符。
native_handle()它返回用于平台特定操作的原生句柄。
swap(thread& other)它在两个线程对象之间交换线程句柄。
hardware_concurrency() (静态)它返回系统支持的线程数(并发线程)。
atomic它可用于以线程安全的方式管理线程之间的共享变量,而无需使用锁。

C++ 线程管理示例

让我们举一个例子来说明 C++ 中线程的几个函数。

示例

编译并运行

输出

System supports 4 concurrent threads.

Thread 2 started. ID: 131265380484800
Thread 3 started. ID: 131265369999040
Thread 1 started. ID: 131265390970560
Thread IDs:
t1: 131265390970560
t2: 131265380484800
t3: 131265369999040
t4: 131265359513280

Thread 4 started. ID: 131265359513280
Thread 3 finished.
Thread 4 finished.
Thread Thread 2 finished.
1 finished.

Main thread ends after managing all threads.

说明

在此示例中,我们通过使用 std::thread 功能来演示 C++ 线程管理。该程序创建了四个线程,同时 join 了另外三个线程,并允许一个线程独立运行。输出显示了线程标识符以及用于并发操作的操作系统详细信息。

多线程的优势

C++ 多线程的几个好处如下:

  • 它有助于提高多核系统的性能。
  • 它为 GUI 应用程序提供了更好的响应性。
  • 它具有高效的资源利用率。
  • 它具有独立任务的并行执行。

多线程中的上下文切换

CPU 执行在不同线程之间执行上下文切换。操作系统需要创建当前线程状态的备份并检索下一个线程的状态,以实现多个线程的运行。多任务操作所需的上下文切换会产生性能开销,当上下文频繁更改时,会特别影响执行速度。

C++ 多线程上下文切换示例

让我们举一个例子来说明 C++ 中多线程的上下文切换。

示例

编译并运行

输出

Total time taken: 0.0120467 seconds

说明

在此示例中,我们找到了使用线程并发运行的两个任务的执行时间。之后,它使用 std::chrono 记录开始和结束时间,创建两个线程来同时执行 task1 和 task2,然后 join 它们,最后计算并打印总耗时。

在 C++ 中向线程传递参数

在 C++ 线程中,我们可以使用 std::thread 构造函数中的结构来传递多个参数。它允许我们将参数复制或移动到线程对象的内部存储中,然后将其传递给可调用对象。

语法

它具有以下语法:

C++ 向线程传递参数示例

让我们举一个例子来说明在 C++ 中向线程传递参数。

示例

编译并运行

输出

Hello! this is from thread 0
Hello! this is from thread 1
Hello! this is from thread 2

说明

在此示例中,我们创建了一个单独的线程来执行 show_message 函数,该函数会显示三次消息。之后,t.join() 调用确保主线程在退出前等待创建的线程完成执行。

多线程问题

C++ 中多线程的几个问题如下:

1) 竞争条件

在 C++ 中,当多个线程并发访问公共数据,并且它们的运行操作顺序取决于它们运行时的时间点时,就会发生竞争条件。如果没有适当的同步,这可能导致数据损坏和不一致的结果。

2) 死锁

在 C++ 中,当多个线程卡在某个状态,它们需要其他线程的资源,同时又相互阻塞时,就会发生死锁,导致它们都无法继续。死锁在应用程序执行过程中发生,因为线程获取的资源锁形成了循环模式。

3) 活锁

在 C++ 中,活锁发生在死锁之间,因为活跃的线程在尝试打破资源冲突时会卡在无限循环中。

4) 饥饿

线程饥饿发生是因为线程需要资源很长时间,但其他线程却一直在占用这些资源。基于优先级的调度方法可能会导致低优先级线程永久被阻塞的死锁。

C++ 多线程选择题

1) 在 C++ 中,如果 std::thread 被创建但未在销毁前 join 或 detach,会发生什么?

  1. 它会自动 join
  2. 程序正常继续
  3. 程序执行将产生一个异常,从而终止正在运行的代码。
  4. 线程在销毁时会自动 detach。

答案:c) 程序执行将产生一个异常,从而终止正在运行的代码。


2) 以下关于 C++ 中 std::async 函数的陈述,哪项是准确的?

  1. std::thread 功能在执行时产生一个独立的线程。
  2. 它会延迟执行直到调用 get()。
  3. 它返回 void。
  4. 它会阻塞调用线程直到完成。

答案:b) 它会延迟执行直到调用 get()。


3) C++ 多线程中 std::mutex 的主要目的是什么?

  1. 暂停线程执行
  2. 创建新线程
  3. 锁定共享资源以实现线程安全
  4. 终止线程

答案:c) 锁定共享资源以实现线程安全


4) 以下哪个选项是使用 C++ 中已分离线程的主要缺点?

  1. 已 join 的线程比已分离的线程运行速度更快。
  2. 父线程无法访问其结果。
  3. 它们消耗更多内存
  4. 所有已分离线程都需要静态声明。

答案:b) 父线程无法访问其结果。


5) 在 C++ 中使用 std::thread 创建线程的以下哪种方式是无效的?

  1. 传递函数指针
  2. 传递 lambda 函数
  3. 传递函子(函数对象)
  4. 调用线程对象的 run()

答案:d) 调用线程对象的 run()