C++ 多线程入门:std::thread 使用详解。C++11 新特性:std::async 、计时器chrono测量代码时间
在 C++11 之后,标准库引入了 std::thread,让我们可以在代码中方便地使用多线程。
这篇文章通过一个简单的例子,来理解线程的启动、运行、同步以及一些注意事项。
一、std::thread(线程)
1. 什么是线程?
线程(Thread)是程序中最小的执行单元。一个程序(进程)中可以有多个线程,它们共享同一个内存空间,但可以同时执行不同的任务。
典型应用场景:
-
在后台执行某个耗时操作(下载文件、日志记录等),避免阻塞主线程。
-
提升程序性能,让多个任务并发执行。
-
构建实时响应的应用(游戏、GUI、服务器)。
2. 一个最简单的线程例子
#include <iostream>
#include <thread>
#include <chrono>
static bool s_Finished = false; // 控制线程退出的标志位
// 线程函数
void DoWork()
{
using namespace std::literals::chrono_literals; // 启用时间字面量 (1s, 500ms 等)
std::cout << "Worker thread id = " << std::this_thread::get_id() << std::endl;
while (!s_Finished) // 当标志位为 false 时持续工作
{
std::cout << "Working...\n";
std::this_thread::sleep_for(1s); // 线程休眠 1 秒,避免 CPU 占用过高
}
}
int main()
{
std::cout << "Main thread id = " << std::this_thread::get_id() << std::endl;
// 创建并启动线程,执行 DoWork
std::thread worker(DoWork);
// 主线程等待用户输入
std::cin.get(); // 在此期间,worker 线程持续输出 "Working..."
s_Finished = true; // 用户按下 Enter 后,通知 worker 线程退出
worker.join(); // 等待 worker 线程完成,确保线程安全退出
std::cout << "Finished.\n";
return 0;
}
3. 输出结果示例
运行时可能看到如下输出:
Main thread id = 10904
Worker thread id = 3932
Working...
Working...
Working...
Working...
Finished.
可以看到:
-
主线程和工作线程的 ID 不一样。
-
在
cin.get()阻塞期间,工作线程仍然在后台输出"Working..."。 -
当用户按下 Enter 后,主线程将
s_Finished置为true,工作线程退出循环,最后join()等待线程结束。
4. 为什么要用 sleep_for?
如果线程函数中没有 sleep,那么 while 循环会疯狂执行,导致 CPU 占用 100%。
错误示例:
while (!s_Finished)
{
std::cout << "Working...\n";
// 没有 sleep,CPU 被占满
}
所以我们在循环中加入 std::this_thread::sleep_for(1s),让线程“休息”一下,既降低 CPU 占用,也更符合实际需求(比如定时任务)。
5. 关于 join
-
worker.join()的作用是让主线程等待工作线程结束。 -
如果不调用
join(),在主线程退出时,工作线程可能还没结束,就会导致程序异常退出。 -
其它语言里(如 Java、C#),类似的功能通常叫 wait / waitForExit。
6. 字面量 1s
using namespace std::literals::chrono_literals;
启用后就可以写:
-
1s→ 1 秒 -
500ms→ 500 毫秒 -
2h→ 2 小时
这些字面量其实是 std::chrono::duration 类型,和 std::this_thread::sleep_for() 完美配合。
7. 总结
通过 std::thread,我们可以轻松地让程序同时执行多个任务。
上面这个例子展示了几个关键点:
-
启动线程:
std::thread worker(DoWork); -
线程循环:通过共享变量
s_Finished控制退出。 -
避免高 CPU 占用:用
sleep_for()给线程降速。 -
线程同步:用
join()等待线程结束,避免野线程。
C++11 新特性:std::async
std::async 是 C++11 引入的一个异步执行机制,用于在后台启动任务并异步获取结果。
它基于线程,但比直接使用 std::thread 更高级,自动管理返回值和生命周期。
一、基础概念
-
std::async会返回一个std::future<T>对象,表示异步任务最终会返回的结果。 -
通过
future.get()可以获取任务的返回值,如果任务未完成,get()会阻塞直到结果就绪。
二、基本语法
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
// -------------------------------
// 模拟耗时计算函数
// -------------------------------
int compute(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return x * x; // 返回平方
}
int main() {
// -------------------------------
// 异步执行 compute(5)
// 返回 std::future<int> 对象
// -------------------------------
std::future<int> result = std::async(compute, 5);
// 主线程可以继续执行其他工作
std::cout << "Doing other work..." << std::endl;
// -------------------------------
// 获取 compute 的返回值
// 如果任务未完成,get() 阻塞等待
// -------------------------------
int value = result.get();
std::cout << "Result: " << value << std::endl; // 输出 25
return 0;
}
输出示例:
Doing other work...
Result: 25
三、调用策略(Launch Policy)
std::async 可以选择两种策略启动任务:
-
std::launch::async-
总是新建线程执行任务
-
保证任务异步执行
std::future<int> f = std::async(std::launch::async, compute, 5); -
-
std::launch::deferred-
延迟执行,只有调用
future.get()或future.wait()时才执行 -
相当于懒惰计算
std::async(std::launch::deferred, compute, 5); -
-
默认策略(两者可能)
std::async(compute, 5);-
实现可以选择异步执行或者延迟执行,具体行为由标准库实现决定。
-
四、返回值与 std::future
-
std::async返回一个std::future<T>对象-
T 是任务函数的返回类型
-
future.get()获取结果 -
如果任务异常,
get()会重新抛出异常
-
std::future<int> f = std::async([](){ return 10; });
int x = f.get(); // x = 10
-
std::future不能拷贝,但可以移动:
std::future<int> f2 = std::move(f);
五、异常处理
-
如果异步任务抛异常,
future.get()会重新抛出异常 -
可以通过 try-catch 捕获:
// -------------------------------
// 创建一个异步任务
// 使用 lambda 表达式作为任务函数
// 异步任务会抛出异常
// -------------------------------
std::future<int> f = std::async([](){
throw std::runtime_error("error in async task"); // 异步任务中抛出异常
return 0; // 这一行不会执行
});
// -------------------------------
// 异步异常处理
// 异步任务异常不会立即抛出,而是在调用 future.get() 时重新抛出
// -------------------------------
try {
f.get(); // 获取异步任务结果,如果任务异常,会在这里抛出
} catch (const std::exception &e) { // 捕获异常
std::cout << "Caught exception: " << e.what() << std::endl;
// 输出:"Caught exception: error in async task"
}
六、常用场景
-
并行计算
-
适合 CPU 密集型任务
-
异步启动多个计算,最后统一
get()
-
-
I/O 或耗时操作
-
异步读取文件、网络请求等
-
主线程可以继续执行其他任务
-
-
懒加载或延迟计算
-
std::launch::deferred可以在需要时才计算
-
七、小结
| 特性 | 说明 |
|---|---|
| 返回类型 | std::future<T> |
| 异步执行 | 可选:std::launch::async / std::launch::deferred |
| 异常处理 | 异步任务的异常会在 get() 时抛出 |
| 适用场景 | CPU 密集计算、耗时操作、懒加载 |
C++ 中使用 chrono 测量代码运行时间
在学习或开发 C++ 程序时,我们经常会想知道:某段代码到底运行了多久?
不是因为“好奇时间”,而是为了 分析性能、验证优化效果。
C++11 引入的 <chrono> 库,提供了跨平台的高精度计时工具,让我们不需要依赖操作系统 API,就能轻松测量运行时间。
1. chrono 的基本用法
我们先来看最简单的例子:
#include <iostream>
#include <chrono>
#include <thread>
int main() {
using namespace std::literals::chrono_literals;
// 记录起始时间
auto start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(1s); // 模拟耗时操作
// 记录结束时间
auto end = std::chrono::high_resolution_clock::now();
// 计算耗时
std::chrono::duration<float> duration = end - start;
std::cout << "耗时: " << duration.count() << " 秒" << std::endl;
return 0;
}
输出示例:
耗时: 1.0079 秒
这里 high_resolution_clock::now() 获取当前时间点,end - start 得到一个 duration(持续时间)。
最后用 .count() 将其转换成数字(单位:秒)。
2. 制作一个自动计时器
手动写 start 和 end 其实很麻烦,C++ 的 RAII 思想(资源获取即初始化)可以帮我们写一个 自动计时器类。
#include <iostream>
#include <chrono>
// 计时器类:利用构造函数 + 析构函数来测量作用域的耗时
struct Timer {
// 定义开始时间点、结束时间点,以及持续时间
std::chrono::time_point<std::chrono::steady_clock> start, end;
std::chrono::duration<float> duration; // 用 float 保存秒数
Timer() {
start = std::chrono::steady_clock::now();
}
~Timer() {
end = std::chrono::steady_clock::now();
// 计算时间差
duration = end - start;
// 转换成毫秒(秒 * 1000)
float ms = duration.count() * 1000.0f;
std::cout << "耗时: " << ms << " 毫秒" << std::endl;
}
};
void Function() {
Timer timer; // 创建 Timer 对象,开始计时
for (int i = 0; i < 100; i++)
std::cout << "Hello\n";
}
int main() {
Function();
}
输出示例:
Hello
Hello
...
Hello
耗时: 18.99 毫秒
这样,我们只需要在函数里写 Timer timer;,就能自动测量作用域的运行时间,非常适合性能分析。
3. 小技巧:std::endl vs \n
在上面的例子中,如果把输出改成:
std::cout << "Hello" << std::endl;
耗时可能会翻几倍。原因是 std::endl 不仅换行,还会强制刷新缓冲区,速度远比 \n 慢。
测试结果:
-
使用
std::endl:约 19ms -
使用
\n:约 5ms
建议:
-
日志、错误信息、交互式提示 → 用
std::endl(确保立即显示) -
普通输出、大量循环打印 → 用
\n(性能更好)
4. chrono 的真正意义
测量运行时间不是为了“看代码用了几秒”,而是为了:
-
性能调试
找出耗时最多的部分,定位性能瓶颈。 -
验证优化
修改代码后,用chrono对比前后耗时,确认优化是否有效。 -
学习效率差异
直观感受不同实现方式的性能差异,比如std::endlvs\n、std::sortvsstd::stable_sort。 -
跨平台一致性
不依赖操作系统 API,Windows / Linux / macOS 上都能稳定运行。
总结
-
chrono是 C++11 提供的跨平台高精度计时工具。 -
可以用
start-end测时间,也可以写Timer类自动计时。 -
主要用途是性能分析和优化验证,而不是单纯“看时间”。
-
结合实际案例,能直观展示不同代码实现的性能差异。
更多推荐


所有评论(0)