面试题:C++ 什么场景用线程?什么场景用协程?

在 C++ 中,**线程(Thread)协程(Coroutine)**是两种并发编程的工具,但它们适用于不同的场景。以下是它们的区别以及适用场景:


1. 线程(Thread)

特点:

  • 操作系统级:线程由操作系统调度,是真正的并发执行。
  • 资源消耗:每个线程都有自己的栈空间(通常几 MB),线程切换开销较大。
  • 同步机制:需要使用互斥锁、条件变量等同步机制来避免资源竞争。
  • 适用场景
    • CPU 密集型任务:如计算、图像处理、加密解密等。
    • 阻塞 I/O 操作:如文件读写、网络通信等。
    • 多核并行计算:利用多核 CPU 的并行能力。

优点:

  • 真正的并发:可以充分利用多核 CPU 的性能。
  • 通用性强:适用于大多数并发场景。

缺点:

  • 资源消耗大:线程数量过多时,内存和调度开销较大。
  • 复杂性高:需要处理线程同步和资源竞争问题。

示例:

#include <iostream>
#include <thread>

void task() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(task);
    t.join();
    return 0;
}

2. 协程(Coroutine)

特点:

  • 用户级:协程由用户程序调度,是协作式多任务。
  • 资源消耗:协程的栈空间较小(通常几 KB),切换开销小。
  • 同步机制:协程之间通常不需要显式同步,因为它们是协作式调度的。
  • 适用场景
    • I/O 密集型任务:如高并发的网络服务器、异步文件读写等。
    • 状态机实现:如游戏逻辑、事件驱动编程等。
    • 轻量级并发:需要大量并发但资源有限的场景。

优点:

  • 资源消耗小:可以创建大量协程,适合高并发场景。
  • 代码简洁:避免了复杂的线程同步问题。

缺点:

  • 非真正并发:协程是协作式调度的,无法利用多核 CPU 的性能。
  • 依赖语言或库支持:C++20 之前需要依赖第三方库(如 Boost.Coroutine)。

示例(C++20):

#include <iostream>
#include <coroutine>

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task coroutine() {
    std::cout << "Hello from coroutine!" << std::endl;
    co_return;
}

int main() {
    coroutine();
    return 0;
}

3. 线程 vs 协程

特性线程(Thread)协程(Coroutine)
调度方式操作系统调度用户程序调度
并发性真正的并发协作式并发
资源消耗每个线程占用几 MB 栈空间每个协程占用几 KB 栈空间
切换开销较大较小
同步机制需要互斥锁、条件变量等通常不需要显式同步
适用场景CPU 密集型任务,阻塞 I/O 操作I/O 密集型任务,轻量级并发
多核利用可以充分利用多核 CPU无法利用多核 CPU
复杂性较高,需要处理线程同步问题较低,代码简洁

4. 选择建议

  • 使用线程的场景
    • 需要真正的并发执行(如多核并行计算)。
    • 需要处理 CPU 密集型任务或阻塞 I/O 操作。
    • 任务之间需要强隔离(如不同的线程处理不同的任务)。
  • 使用协程的场景
    • 需要高并发处理 I/O 密集型任务(如网络服务器)。
    • 需要轻量级并发,且资源有限。
    • 任务之间需要协作式调度(如状态机、事件驱动编程)。

5. 结合使用

在实际项目中,线程和协程可以结合使用:

  • 使用线程处理 CPU 密集型任务。
  • 使用协程处理 I/O 密集型任务。
  • 例如,在网络服务器中,可以使用线程池处理计算任务,同时使用协程处理网络 I/O。

示例(伪代码):

void handle_io() {
    // 使用协程处理 I/O
    while (true) {
        co_await async_read();
        co_await async_write();
    }
}

void handle_compute() {
    // 使用线程处理计算
    while (true) {
        perform_computation();
    }
}

int main() {
    std::thread compute_thread(handle_compute);
    handle_io();  // 在主线程中运行协程
    compute_thread.join();
    return 0;
}

总结

  • 线程:适合 CPU 密集型任务、阻塞 I/O 操作和多核并行计算。
  • 协程:适合 I/O 密集型任务、轻量级并发和协作式调度。
  • 在实际项目中,可以根据任务特性选择合适的工具,甚至结合使用线程和协程。
THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容