多线程 C++

CS106L p18 Part 6.2 Multithreading

多线程

C++中的multithreading(多线程)是指在程序中并发地执行多个线程的能力。线程是程序执行的基本单元,而多线程则允许程序在同一时间内处理多个任务,通常用来提高程序的效率,尤其是在多核处理器上,通过并行处理来减少任务的总执行时间。多线程广泛应用于高性能计算、图形渲染、网络通信、游戏开发等领域。

1. 线程的基本概念

线程是程序中的一个执行流,它有自己的执行栈、程序计数器和局部变量。多个线程共享同一进程的资源,例如内存、文件句柄等。相比于单一的执行流(即单线程),多线程能够让程序在同一时刻执行多个任务,从而更充分地利用多核处理器的计算能力。

线程和进程的区别:

  • 进程是资源分配的基本单位,包含了地址空间、数据、堆栈等。
  • 线程是程序执行的基本单位,属于进程的一部分,它共享进程的资源。

在C++中,线程的创建、管理和同步是通过标准库提供的工具实现的,尤其是C++11引入的<thread>库和相关功能。

2. C++中的多线程实现

从C++11开始,C++标准库加入了对多线程的直接支持,提供了<thread>头文件,其中包括了创建和管理线程的类和函数。以下是一些常见的多线程编程元素:

2.1 线程的创建与执行

要创建一个线程,首先需要定义一个函数或者可调用对象(如函数指针、lambda表达式等),然后将其传递给std::thread类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>

void hello() {
std::cout << "Hello from thread!" << std::endl;
}

int main() {
// 创建并启动线程
std::thread t(hello);

// 等待线程结束
t.join();

return 0;
}

在上述代码中:

  • std::thread t(hello);:创建并启动一个新线程,执行hello函数。
  • t.join();:主线程等待子线程t执行完成。

2.2 传递参数到线程

如果需要传递参数给线程,可以直接在创建线程时传递。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <thread>

void print_number(int x) {
std::cout << "The number is: " << x << std::endl;
}

int main() {
std::thread t(print_number, 5); // 向线程传递参数5
t.join();

return 0;
}

这里,print_number函数接收一个参数x,并在线程中打印出来。

2.3 线程的同步与互斥

多线程编程的核心挑战之一是共享资源的同步问题。当多个线程访问同一资源时,可能会发生数据竞争(data race),导致不一致的结果。为了防止这种情况,可以使用互斥锁(mutex)来同步对共享资源的访问。

C++标准库提供了std::mutex来实现互斥锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_hello() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁与解锁
std::cout << "Hello from thread!" << std::endl;
}

int main() {
std::thread t1(print_hello);
std::thread t2(print_hello);

t1.join();
t2.join();

return 0;
}

在上述代码中,std::mutex mtx是一个互斥锁,std::lock_guard<std::mutex>用于自动加锁和解锁。lock_guard确保在作用域结束时,锁会自动被释放,从而避免死锁的发生。

2.4 线程的并发和并行

  • 并发(Concurrency)指的是多个任务在同一时间段内交替执行,可能是在单核或多核处理器上。在多核处理器上,任务可以在不同核心上并行执行,但并不一定同时执行。
  • 并行(Parallelism)是指多个任务在同一时刻在不同的处理器核心上执行,通常只有在拥有多核CPU的系统中,才能实现真正的并行。

C++的多线程通常提供并发执行的能力,实际的并行执行取决于硬件和操作系统的调度。

2.5 线程的同步机制:条件变量

在多线程程序中,有时需要让某些线程等待某些条件发生。这种情况下,可以使用条件变量(std::condition_variable

条件变量用于在多线程中同步事件的发生。可以在某个线程上等待某个条件满足,然后再继续执行。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
std::unique_lock<std::mutex> lck(mtx);
while (!ready) cv.wait(lck); // 等待条件
std::cout << "Thread " << id << '\n';
}

void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true; // 设置条件为真
cv.notify_all(); // 通知所有等待的线程
}

int main() {
std::thread threads[10];

// 创建多个线程
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);

std::cout << "Ready... set... go!" << std::endl;
go(); // 改变条件,通知所有线程开始执行

for (auto& t : threads) t.join(); // 等待所有线程结束
return 0;
}

在这个例子中,print_id函数的线程会等待ready变量变为true,只有在go()函数中通知后,所有线程才会开始执行。

3. 多线程中的常见问题

多线程编程虽然能够带来性能上的优势,但也伴随着许多挑战和潜在问题,包括但不限于:

  • 数据竞争(Data Race):多个线程并发地读写共享数据,导致数据不一致。
  • 死锁(Deadlock):多个线程因互相等待对方释放资源,导致程序无法继续执行。
  • 活锁(Livelock):类似死锁,线程不断尝试某些操作,但始终无法完成任务。
  • 优先级反转(Priority Inversion):低优先级的线程占用了资源,导致高优先级线程无法执行。

为了避免这些问题,必须合理设计线程同步和资源管理策略。

4. 总结

C++中的多线程编程能够帮助开发者更好地利用现代多核处理器,提高程序的并发性和执行效率。C++11引入了强大的线程支持,包括线程创建、同步机制(如互斥锁、条件变量)等。掌握多线程的基本操作和同步原理是开发高效、稳定应用程序的关键。然而,多线程编程也需要小心避免数据竞争、死锁等问题,因此需要更加细致地设计和调试。


多线程 C++
http://wang-jiahao.github.io/posts/28135.html
作者
Jiahao Wang
发布于
2024年12月5日
更新于
2024年12月11日
许可协议