c++:线程(std::thread)
目录
从第一性原理出发:为什么需要线程?
✅ 本质定义:
📌 使用基本语法:
线程之间的“并发”与“并行”的区别
线程安全与数据竞争(Race Condition)
如何让线程“安全地”访问数据?
完整示例:使用线程加速加法
从第一性原理出发:为什么需要线程?
想象一下:
你有一个程序,它要做很多事,比如:
-
下载文件
-
处理图片
-
打印日志
-
响应用户输入如果这些任务按顺序来(单线程),那用户体验就会很差:比如下载还没完,界面就卡住了。
第一性问题来了:
能不能让程序同时做多件事,而不是排队一个个来?
能!用线程!
✅ 本质定义:
线程(Thread)是程序内部能并发执行的最小单位。
线程 = 程序里可以独立并发执行的一段任务。
一个程序至少有一个线程(主线程),你可以再创建多个线程来同时执行不同的代码块。
📌 使用基本语法:
#include <thread>
std::thread
类就是 C++ 提供的“创建和管理线程”的工具。
#include <iostream>
#include <thread>void work() {std::cout << "工作线程正在运行\n";
}int main() {std::thread t(work); // 创建一个线程执行 work()t.join(); // 等待这个线程执行完std::cout << "主线程结束\n";
}
解释:
-
std::thread t(work);
:创建了一个“工人线程”,去执行函数work
-
t.join();
:告诉主线程“等工人做完再走” -
最后主线程输出“主线程结束”
创建线程的三种方式:
方法 | 示例 |
---|---|
传函数名(函数指针) | std::thread t(func); |
传 lambda 表达式 | std::thread t([](){ ... }); |
传函数对象(仿函数) | std::thread t(Functor()); |
线程的控制操作(join
和 detach
)
std::thread t(func);
你创建了一个新线程,但它和主线程是“并行运行的”。
接下来你必须做一件事(二选一):
操作 | 说明 |
---|---|
t.join() | 等待这个线程运行结束,和主线程“合并” |
t.detach() | 把线程“放飞”,让它自己跑,主线程不再管它(后台线程) |
C++ 标准要求:每个 std::thread
对象 在销毁前必须被 join 或 detach!
注意:主线程可能在子线程还没结束时就退出了,这种线程叫做 后台线程。
线程之间的“并发”与“并行”的区别
概念 | 含义 |
---|---|
并发(concurrent) | 逻辑上“同时”运行,实际上轮流交替执行(单核CPU) |
并行(parallel) | 真正的同时执行(多核CPU,每个线程跑在不同核上) |
线程安全与数据竞争(Race Condition)
多个线程访问同一块数据会出问题!
int counter = 0;void add() {for (int i = 0; i < 100000; ++i)++counter;
}int main() {std::thread t1(add);std::thread t2(add);t1.join();t2.join();std::cout << counter << std::endl; // ❓ 输出不是 200000?!
}
为什么?
-
因为两个线程在同时修改同一个变量
-
出现了所谓的 数据竞争(Race Condition)
如何让线程“安全地”访问数据?
使用互斥锁(std::mutex
)
#include <mutex>std::mutex mtx;void add() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 自动加锁/解锁++counter;}
}
使用 lock_guard
是现代 C++ 推荐的做法,RAII 自动释放锁。
完整示例:使用线程加速加法
#include <iostream>
#include <thread>void printRange(int start, int end) {for (int i = start; i <= end; ++i)std::cout << i << " ";
}int main() {std::thread t1(printRange, 1, 50);std::thread t2(printRange, 51, 100);t1.join();t2.join();std::cout << "\nDone\n";
}
输出可能是:
1 2 3 51 52 53 ... 50 100
顺序是“乱的”!因为是并发执行的结果。