C++: 多线程基础
最后更新:2026-08-26
第42课我们学了正则表达式。
现在,我们要进入C++的高级领域——多线程编程。
现代计算机都是多核的,单线程程序只能用到一个核,太浪费了。
多线程让你同时做好几件事,大幅提升性能。
1. 多线程概述
(1) 1.1 什么是线程?
线程(Thread)是程序执行的最小单位。
进程 vs 线程:
- 进程:资源分配单位(独立内存空间)
- 线程:执行单位(共享进程内存)
生活类比:
- 进程 = 工厂
- 线程 = 工人(多个工人共享工厂资源)
(2) 1.2 为什么要用多线程?
| 优势 | 说明 |
|---|---|
| 提高性能 | 多核并行计算 |
| 提高响应 | UI线程不阻塞 |
| 简化设计 | 把不同任务分给不同线程 |
2. 创建线程
(1) 2.1 基本用法
C++11在 thread 头文件中提供了 std::thread 类。
示例:创建线程(难度⭐)
▶ 示例 1:多线程编程演示(难度⭐)
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello); // 创建线程
t.join(); // 等待线程结束
std::cout << "Main thread ends" << std::endl;
return 0;
}
输出:
主线程:开始
主线程:等待子线程完成
子线程:Hello from thread
主线程:结束
运行结果:
Hello from thread!
Main thread ends
(2) 2.2 join vs detach
| 函数 | 功能 | 说明 |
|---|---|---|
join() |
等待线程结束 | 阻塞当前线程 |
detach() |
分离线程 | 线程独立运行,无法再join |
示例:用join等待(难度⭐)
#include <iostream>
### ▶ 示例 2:多线程编程演示(难度⭐)
#include <thread>
#include <chrono>
void worker(int id) {
for (int i = 0; i < 3; i++) {
std::cout << "Worker " << id << " working..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join(); // 等待t1结束
t2.join(); // 等待t2结束
std::cout << "All workers done" << std::endl;
return 0;
}
3. 线程参数传递
(1) 3.1 传递参数
std::thread 构造函数可以接受任意可调用对象和参数。
示例:传递参数(难度⭐⭐)
#include <iostream>
#include <thread>
#include <string>
void printMessage(std::string msg, int count) {
for (int i = 0; i < count; i++) {
std::cout << msg << std::endl;
}
}
int main() {
std::thread t(printMessage, "Hello", 3);
t.join();
return 0;
}
(2) 3.2 引用传递
默认情况下,参数按值传递。要用引用,必须用 std::ref。
示例:用引用传递(难度⭐⭐)
#include <iostream>
#include <thread>
#include <functional>
void increment(int& x) {
x++;
}
int main() {
int counter = 0;
std::thread t(increment, std::ref(counter));
t.join();
std::cout << "Counter: " << counter << std::endl; // 输出:1
return 0;
}
4. 互斥量
(1) 4.1 为什么需要互斥量?
问题: 多个线程同时访问共享数据,会导致数据竞争(Data Race)。
示例:数据竞争(难度⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
counter++; // 多个线程同时修改,结果不确定
}
}
int main() {
std::vectorstd::thread threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 期望10000,实际可能少于
return 0;
}
(2) 4.2 用互斥量保护共享数据
互斥量(Mutex)用于保证同一时间只有一个线程访问共享数据。
示例:用mutex保护(难度⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; i++) {
mtx.lock(); // 加锁
counter++;
mtx.unlock(); // 解锁
}
}
int main() {
std::vectorstd::thread threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 一定是10000
return 0;
}
(3) 4.3 lock_guard——RAII方式
推荐用法: 用 std::lock_guard 自动加锁/解锁。
void increment() {
for (int i = 0; i < 1000; i++) {
std::lock_guardstd::mutex lock(mtx); // 构造时加锁,析构时解锁
counter++;
} // 自动解锁
}
5. 条件变量
(1) 5.1 为什么需要条件变量?
问题: 线程需要等待某个条件成立(如队列非空)。
解决方案: std::condition_variable
(2) 5.2 示例:生产者-消费者(难度⭐⭐⭐)
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
std::queueint q;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; i++) {
std::lock_guardstd::mutex lock(mtx);
q.push(i);
std::cout << "生产:" << i << std::endl;
cv.notify_one(); // 通知消费者
}
}
void consumer() {
for (int i = 0; i < 10; i++) {
std::unique_lockstd::mutex lock(mtx);
cv.wait(lock, { return !q.empty(); }); // 等待队列非空
int value = q.front();
q.pop();
std::cout << "消费:" << value << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
输出:
生产:
消费:
6. 异步任务
(1) 6.1 std::async
std::async 用于启动异步任务,返回 std::future。
示例:异步计算(难度⭐⭐)
#include <iostream>
#include <future>
int calculate(int x) {
return x * x;
}
int main() {
std::futureint result = std::async(calculate, 10);
std::cout << "结果:" << result.get() << std::endl; // 输出:100
return 0;
}
❓ 常见问题
Q:多少线程合适? A:通常等于CPU核心数。太多会导致上下文切换开销。
Q:死锁是什么? A:两个线程互相等待对方释放锁,导致谁也无法继续。
避免方法:
- 按固定顺序加锁
- 用
std::lock()同时锁多个互斥量 - 用
std::scoped_lock(C++17)
std::thread:灵活,跨平台 - OpenMP:简单,适合科学计算▶ 示例 3:创建线程(难度⭐)
#include <iostream>
#include <thread>
void printNumbers(int start, int end) {
for (int i = start; i <= end; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
std::thread t1(printNumbers, 1, 5);
std::thread t2(printNumbers, 10, 15);
t1.join();
t2.join();
return 0;
}
输出:
输出(示例):
1 10 2 11 3 12 4 13 5 14 15
std::thread 创建线程,join() 等待线程结束。输出顺序可能因线程调度而不同。
| 知识点 | 要点 |
|---|---|
| std::thread | 创建线程 |
| join/detach | 等待/分离线程 |
| std::mutex | 互斥量,保护共享数据 |
| std::lock_guard | RAII方式加锁 |
| std::condition_variable | 条件变量,线程间通信 |
| std::async | 异步任务 |
📖 小节
- std::thread:创建线程
- join():等待线程结束
- detach():分离线程
- 线程函数:可传函数指针、Lambda、函数对象
📝 作业
-
**基础题 (Difficulty ⭐):创建两个线程,分别输出"线程A"和"线程B",观察输出顺序的随机性。
-
**进阶题 (Difficulty ⭐⭐):创建 4 个线程,每个线程计算一段数值的累加(如 1-2500、2501-5000...),最后汇总累加结果。
-
**挑战题 (Difficulty ⭐⭐⭐):用 std::async 和 std::future 实现一个并发下载模拟器:创建 3 个异步任务,每个模拟下载不同大小的文件,等待全部完成后汇总。
- std::thread 创建线程,传入可调用对象
- join 等待线程结束,detach 分离线程
- 线程之间共享全局变量需同步
- std::this_thread::sleep_for 让线程休眠
- 线程数量不宜超过硬件支持数(hardware_concurrency)
下一课:多线程同步(#44)