在 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,我们可以轻松地让程序同时执行多个任务。
上面这个例子展示了几个关键点:

  1. 启动线程std::thread worker(DoWork);

  2. 线程循环:通过共享变量 s_Finished 控制退出。

  3. 避免高 CPU 占用:用 sleep_for() 给线程降速。

  4. 线程同步:用 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 可以选择两种策略启动任务:

  1. std::launch::async

    • 总是新建线程执行任务

    • 保证任务异步执行

    std::future<int> f = std::async(std::launch::async, compute, 5);
    
  2. std::launch::deferred

    • 延迟执行,只有调用 future.get()future.wait() 时才执行

    • 相当于懒惰计算

    std::async(std::launch::deferred, compute, 5);

  3. 默认策略(两者可能)

    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"
    }

六、常用场景

  1. 并行计算

    • 适合 CPU 密集型任务

    • 异步启动多个计算,最后统一 get()

  2. I/O 或耗时操作

    • 异步读取文件、网络请求等

    • 主线程可以继续执行其他任务

  3. 懒加载或延迟计算

    • 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. 制作一个自动计时器

手动写 startend 其实很麻烦,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 的真正意义

测量运行时间不是为了“看代码用了几秒”,而是为了:

  1. 性能调试
    找出耗时最多的部分,定位性能瓶颈。

  2. 验证优化
    修改代码后,用 chrono 对比前后耗时,确认优化是否有效。

  3. 学习效率差异
    直观感受不同实现方式的性能差异,比如 std::endl vs \nstd::sort vs std::stable_sort

  4. 跨平台一致性
    不依赖操作系统 API,Windows / Linux / macOS 上都能稳定运行。


总结

  • chrono 是 C++11 提供的跨平台高精度计时工具。

  • 可以用 start-end 测时间,也可以写 Timer 类自动计时。

  • 主要用途是性能分析和优化验证,而不是单纯“看时间”。

  • 结合实际案例,能直观展示不同代码实现的性能差异。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐