C++ 线程与协程的选择指南:从场景到实践

📅 2026/7/26 1:14:44 👁️ 阅读次数 📝 编程学习
C++ 线程与协程的选择指南:从场景到实践

C++ 线程与协程的选择指南:从场景到实践



一、引言:并发编程的两种范式


线程(Thread)和协程(Coroutine)是 C++ 中实现并发的两种核心机制。线程由操作系统调度,是 CPU 调度的最小单位;协程由程序自身调度,是用户态的“轻量级线程”。选择错误的工具会导致性能瓶颈、资源浪费或代码复杂度失控。


理解两者的本质差异和适用边界,是构建高性能 C++ 应用的基础。



二、核心区别速览



| 维度 | 线程 (std::thread) | 协程 (C++20 Coroutine) |

| --- | --- | --- |

| 调度者 | 操作系统内核(抢占式) | 用户代码/事件循环(协作式) |

| 切换开销 | 高(系统调用、上下文切换,~1-10μs) | 极低(函数调用级别,~10-100ns) |

| 内存开销 | 大(每线程独立栈,通常 1-8MB) | 极小(仅堆上协程帧,通常几十到几百字节) |

| 并发数 | 有限(数百到数千) | 极高(数万到数百万) |

| 并行性 | 真正并行(多核同时执行) | 单线程内并发,非并行 |

| 阻塞操作 | 线程阻塞不影响其他线程 | 协程阻塞会阻塞整个线程 |

| 编程模型 | 复杂(锁、同步、数据竞争) | 相对简单(无锁协作式) |

| 适用场景 | CPU 密集型、阻塞 I/O、需要并行 | 高并发 I/O、非阻塞异步、生成器 |



三、使用线程的场景



3.1 CPU 密集型计算


线程是唯一能实现真正并行计算的方式,可以利用多核 CPU 同时执行。


cpp复制下载

#include <thread> #include <vector> #include <numeric> #include <iostream> // CPU 密集型:矩阵乘法、图像处理、科学计算 void parallelSum(const std::vector<int>& data, size_t start, size_t end, long long& result) { result = std::accumulate(data.begin() + start, data.begin() + end, 0LL); } int main() { std::vector<int> data(10'000'000, 1); long long sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; // 利用 4 核并行计算 size_t chunk = data.size() / 4; std::thread t1(parallelSum, std::ref(data), 0, chunk, std::ref(sum1)); std::thread t2(parallelSum, std::ref(data), chunk, 2*chunk, std::ref(sum2)); std::thread t3(parallelSum, std::ref(data), 2*chunk, 3*chunk, std::ref(sum3)); std::thread t4(parallelSum, std::ref(data), 3*chunk, data.size(), std::ref(sum4)); t1.join(); t2.join(); t3.join(); t4.join(); std::cout << "Total: " << sum1 + sum2 + sum3 + sum4 << std::endl; }



3.2 阻塞式系统调用


当操作涉及阻塞式 I/O(如读文件、等待用户输入),线程可以在阻塞时让出 CPU。


cpp复制下载

// 阻塞 I/O:文件读写、socket 阻塞模式、数据库查询 void blockingFileRead(const std::string& filename) { std::ifstream file(filename, std::ios::binary); std::vector<char> buffer(1024 * 1024); // 1MB file.read(buffer.data(), buffer.size()); // 阻塞操作,但不影响其他线程 // 处理数据... } std::thread readerThread(blockingFileRead, "large_file.bin"); // 主线程可以做其他事情 doOtherWork(); readerThread.join();



3.3 需要与现有同步模型集成


cpp复制下载

// 使用 condition_variable 实现生产者-消费者 class ThreadSafeQueue { std::queue<int> queue_; mutable std::mutex mtx_; std::condition_variable cv_; public: void push(int value) { std::lock_guard lock(mtx_); queue_.push(value); cv_.notify_one(); // 唤醒等待的线程 } int pop() { std::unique_lock lock(mtx_); cv_.wait(lock, [this] { return !queue_.empty(); }); int value = queue_.front(); queue_.pop(); return value; } };



四、使用协程的场景



4.1 高并发 I/O 密集型服务


协程最适合处理大量并发连接的网络服务,避免为每个连接创建线程。


cpp复制下载

#include <boost/asio.hpp> #include <boost/asio/awaitable.hpp> #include <boost/asio/co_spawn.hpp> using boost::asio::awaitable; using boost::asio::co_spawn; using boost::asio::use_awaitable; // 协程处理单个客户端连接 awaitable<void> handleClient(tcp::socket socket) { try { char buffer[1024]; while (true) { // 异步读取,不阻塞线程 size_t n = co_await socket.async_read_some( boost::asio::buffer(buffer), use_awaitable); // 异步写入 co_await async_write(socket, boost::asio::buffer(buffer, n), use_awaitable); } } catch (const std::exception& e) { std::cerr << "Client error: " << e.what() << std::endl; } } // 主协程:接受连接 awaitable<void> listener(tcp::acceptor& acceptor) { while (true) { auto socket = co_await acceptor.async_accept(use_awaitable); co_spawn(acceptor.get_executor(), handleClient(std::move(socket)), detached); } } int main() { boost::asio::io_context ioContext; tcp::acceptor acceptor(ioContext, tcp::endpoint(tcp::v4(), 8080)); co_spawn(ioContext, listener(acceptor), detached); ioContext.run(); // 事件循环驱动所有协程 }


性能对比:在 10 万并发连接场景下,使用线程可能需要 10 万个线程(每个 8MB 栈 = 800GB 内存),而协程仅需 10 万个协程帧(每个约 100 字节 = 10MB 内存)。



4.2 生成器和惰性求值


协程可以自然地表达惰性序列,按需产生值而非一次性计算全部结果。


cpp复制下载

#include <coroutine> #include <iostream> #include <optional> // 生成器:按需产生斐波那契数列 template<typename T> struct Generator { struct promise_type { T currentValue; auto get_return_object() { return Generator{this}; } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { return std::suspend_always{}; } auto yield_value(T value) { currentValue = value; return std::suspend_always{}; } void return_void() { } void unhandled_exception() { std::terminate(); } }; struct iterator { std::coroutine_handle<promise_type> handle; bool operator!=(std::default_sentinel_t) const { return !handle.done(); } iterator& operator++() { handle.resume(); return *this; } T operator*() const { return handle.promise().currentValue; } }; iterator begin() { handle.resume(); return {handle}; } std::default_sentinel_t end() { return {}; } private: explicit Generator(promise_type* p) : handle(std::coroutine_handle<promise_type>::from_promise(*p)) { } std::coroutine_handle<promise_type> handle; }; // 斐波那契数列生成器 Generator<int> fibonacci(int limit) { int a = 0, b = 1; for (int i = 0; i < limit; ++i) { co_yield a; int temp = a; a = b; b = temp + b; } } int main() { // 惰性生成:只在需要时计算下一个值 for (int value : fibonacci(10)) { std::cout << value << " "; // 0 1 1 2 3 5 8 13 21 34 } }



4.3 异步操作的顺序编排


协程用同步风格编写异步代码,避免“回调地狱”。


cpp复制下载

// 协程:像同步代码一样编排异步操作 awaitable<void> processUserData(const std::string& userId) { // 顺序执行多个异步操作,代码风格像同步 auto userInfo = co_await fetchUserInfo(userId); // 异步查询用户 auto orders = co_await fetchUserOrders(userId); // 异步查询订单 auto recommendations = co_await fetchRecommendations(userInfo); // 异步推荐 // 处理结果 renderPage(userInfo, orders, recommendations); }



五、选择决策流程


图表代码下载全屏

六、混合使用:线程 + 协程


在实际项目中,线程和协程经常混合使用,发挥各自优势:


cpp复制下载

// 多线程 + 每线程内协程:充分利用多核 + 高并发 I/O class HybridServer { std::vector<std::thread> workerThreads_; void workerLoop(boost::asio::io_context& ioCtx) { ioCtx.run(); // 事件循环驱动协程 } public: void start(int numThreads) { boost::asio::io_context ioCtx; // 在事件循环上启动协程 co_spawn(ioCtx, listener(ioCtx), detached); // 启动多个工作线程(通常等于 CPU 核心数) for (int i = 0; i < numThreads; ++i) { workerThreads_.emplace_back(&HybridServer::workerLoop, this, std::ref(ioCtx)); } } }; // 这种设计在 Nginx、Redis、Seastar 等高性能系统中广泛应用



七、总结


线程和协程的选择可以归纳为以下核心原则:



  1. 线程用于 CPU 密集和真正并行:需要利用多核 CPU 的计算能力时必须使用线程。线程由操作系统抢占式调度,适合计算密集型任务、阻塞式系统调用和需要与现有同步模型集成的场景。缺点是内存开销大(每线程独立栈)、切换代价高。
  2. 协程用于高并发 I/O 和异步编排:协程在用户态通过事件循环协作式调度,切换代价极低,内存占用小。适合高并发网络服务、生成器/惰性求值和异步操作的顺序编排。缺点是不提供真正并行,协程内的阻塞操作会阻塞整个线程。
  3. 混合使用是现代高性能系统的最佳实践:使用少量线程(通常等于 CPU 核数)充分利用多核,每个线程内运行大量协程处理海量并发连接。这种架构兼具多核并行和高并发 I/O 的优势。
  4. 选择的关键因素



  • 任务性质:CPU 密集型 → 线程;I/O 密集型 → 协程
  • 并发规模:低并发 → 线程池;高并发(万级+)→ 协程
  • 编程复杂度:同步阻塞代码 → 线程;异步非阻塞 → 协程
  • 资源限制:内存紧张 → 协程;内存充裕 → 线程也可接受


线程和协程并非互斥,而是互补。在高性能 C++ 应用中,合理的架构往往是“少量线程 + 大量协程”的组合,各取所长。