使用std::async实现光线追踪时如何在主线程显示进度条
实现主线程控制的光线追踪进度条(基于std::async)
这个需求其实很好解决,核心就是用一个线程安全的计数器让所有异步像素任务共享进度状态,然后主线程单独负责轮询进度并打印动态进度条——完全不用在异步任务里做任何IO操作,完美符合你要的“仅主线程打印”的要求。
关键思路拆解
- 线程安全的进度计数器:用
std::atomic<int>统计已完成的像素数,它能保证多线程环境下的计数操作原子性,不需要额外加互斥锁,性能开销极低。 - 异步任务绑定计数更新:每个(或每批)像素计算完成后,让原子计数器递增。如果你的渲染是按行/分块处理的,建议按块更新计数器,减少原子操作的次数,进一步优化性能。
- 主线程轮询打印进度:启动所有异步任务后,主线程进入循环,定期读取计数器值,计算进度百分比,用终端的回车符
\r实现进度条的动态覆盖更新,避免刷屏。
完整代码示例
假设你的光线追踪器已经有了compute_pixel(int x, int y)函数负责单个像素的计算,下面是整合进度条的代码:
#include <atomic> #include <vector> #include <future> #include <iostream> #include <iomanip> #include <chrono> #include <thread> // 假设你的分辨率参数 const int WIDTH = 1920; const int HEIGHT = 1080; const int TOTAL_PIXELS = WIDTH * HEIGHT; // 线程安全的已完成像素计数器 std::atomic<int> completed_pixels(0); // 你的像素计算函数(示例) void compute_pixel(int x, int y) { // 这里替换成你的光线追踪逻辑:发射光线、求交、计算颜色等 // ... } int main() { std::vector<std::future<void>> render_tasks; // 启动异步像素计算任务(按行分组,减少原子操作次数) for (int y = 0; y < HEIGHT; ++y) { render_tasks.emplace_back(std::async(std::launch::async, [y]() { // 计算当前行的所有像素 for (int x = 0; x < WIDTH; ++x) { compute_pixel(x, y); } // 整行完成后,原子更新计数器 completed_pixels.fetch_add(WIDTH, std::memory_order_relaxed); })); } // 主线程:动态打印进度条 while (completed_pixels < TOTAL_PIXELS) { int current = completed_pixels.load(std::memory_order_relaxed); float progress = static_cast<float>(current) / TOTAL_PIXELS * 100.0f; // 构建进度条字符串 std::cout << "\rRendering: ["; const int BAR_WIDTH = 50; int bar_pos = static_cast<int>(progress / 100.0f * BAR_WIDTH); for (int i = 0; i < BAR_WIDTH; ++i) { if (i < bar_pos) std::cout << "="; else if (i == bar_pos) std::cout << ">"; else std::cout << " "; } // 打印百分比,保留一位小数 std::cout << "] " << std::fixed << std::setprecision(1) << progress << "%"; std::cout.flush(); // 强制输出,避免缓存导致的显示延迟 // 短暂休眠,避免主线程空转占用过多CPU std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // 等待所有异步任务完全结束(保险起见,避免遗漏异常) for (auto& task : render_tasks) { task.get(); } // 渲染完成后换行,恢复终端正常输出 std::cout << "\nRender completed successfully!" << std::endl; return 0; }
细节优化提示
- 按块更新计数器:上面的示例是按行更新,如果你是分更大的块(比如16x16的像素块),可以进一步减少原子操作的调用次数,提升性能。
- 调整刷新频率:
sleep_for的时间可以根据你的需求调整,比如改成50ms会让进度条更平滑,但CPU占用会略高;200ms则更节省资源。 - 异常处理:如果你的
compute_pixel可能抛出异常,记得在task.get()的时候用try-catch包裹,避免程序崩溃。
内容的提问来源于stack exchange,提问作者Alonbs
相关产品推荐
相关产品推荐

