C++高级编程(7)-- 协程在C++中的应用

协程在C++中的应用

随着现代编程范式的发展,协程(Coroutines)已经成为了并发编程和异步编程的一个重要工具。在C++中,协程是一种能够被挂起和重新恢复执行的函数,它允许程序员以一种更加直观和简洁的方式来编写异步代码。本篇博客将深入探讨C++中的协程概念、实现原理以及高级应用案例,帮助读者更好地理解和应用这一强大的编程技术。

协程基础

在C++20标准中,协程是通过关键字co_await, co_yieldco_return来实现的。这些关键字可以用于生成器(generators)、异步任务(async tasks)和其它形式的协程。

生成器

生成器是一种特殊类型的协程,它可以逐步产生一系列的值,而不是一次性计算所有值。这在处理大量数据或无限序列时非常有用。

#include <coroutine>
#include <iostream>

template<typename T>
struct Generator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type {
        T value;
        bool done;
        auto get_return_object() { return Generator{handle_type::from_address(this)}; }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void unhandled_exception() { done = true; }
        auto yield_value(T v) {
            value = v;
            return std::suspend_always{};
        }
        void return_void() { done = true; }
    };
    bool move_next() { return !coro.done; }
    T current_value() { return coro.promise().value; }
    ~Generator() { if(coro) coro.destroy(); }
private:
    Generator(handle_type h) : coro(h) {}
    handle_type coro;
};

Generator<int> range(int from, int to) {
    for (int i = from; i < to; ++i) {
        co_yield i;
    }
}

int main() {
    auto r = range(1, 5);
    while (r.move_next()) {
        std::cout << r.current_value() << ' ';
    }
    std::cout << '';
    return 0;
}

在这个例子中,range函数是一个生成器,它逐步产生一个范围内的整数序列。通过使用co_yield关键字,我们可以挂起函数的执行,并在每次迭代时恢复执行并产生下一个值。

异步任务

异步任务是另一种常见的协程应用,它允许我们在不阻塞主线程的情况下执行耗时的操作。

#include <coroutine>
#include <future>
#include <iostream>
#include <thread>

template<typename T>
struct AsyncTask {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type {
        std::future<T> result;
        auto get_return_object() { return AsyncTask{handle_type::from_address(this)}; }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void unhandled_exception() { result.set_exception(std::current_exception()); }
        auto yield_value(T v) { result = v; return std::suspend_always{}; }
        void return_void() { result.set_value(); }
    };
    bool move_next() { return !coro.done; }
    T get_result() { return coro.promise().result.get(); }
    ~AsyncTask() { if(coro) coro.destroy(); }
private:
    AsyncTask(handle_type h) : coro(h) {}
    handle_type coro;
};

AsyncTask<int> async_add(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    co_return a + b;
}

int main() {
    auto task = async_add(3, 4);
    std::cout << "Waiting for result...";
    std::cout << "Result: " << task.get_result() << '';
    return 0;
}

在这个例子中,async_add函数是一个异步任务,它在一个单独的线程中执行加法运算,并通过co_return关键字返回结果。通过使用协程,我们可以避免显式的线程管理和同步操作,使得异步代码更加简洁和易于理解。

高级应用

除了基本的生成器和异步任务之外,协程还可以用于更复杂的场景,如协程调度器、协程池和通道(channels)。

协程调度器

协程调度器是一个管理协程执行的组件,它可以在不同的协程之间切换执行权,以达到更高的并发性能。在C++中,我们可以使用std::coroutine_handle来控制协程的执行。

#include <coroutine>
#include <iostream>
#include <vector>
#include <queue>

template<typename T>
class Scheduler {
public:
    void push(T&& coro) {
        coroutines.emplace([&]() -> T {
            runningCoroutines++;
            try {
                auto handle = coro.begin();
                while (!handle.done) {
                    schedule(handle);
                }
            } catch (...) {
                runningCoroutines--;
                throw;
            }
            runningCoroutines--;
            return T{};
        });
    }

    void schedule(std::coroutine_handle<T> handle) {
        if (runningCoroutines >= maxConcurrency) {
            waitingCoroutines.push(handle);
        } else {
            runningCoroutines++;
            handle();
            runningCoroutines--;
            if (!waitingCoroutines.empty()) {
                schedule(waitingCoroutines.front());
                waitingCoroutines.pop();
            }
        }
    }

private:
    std::vector<std::jthread> coroutines;
    std::queue<std::coroutine_handle<T>> waitingCoroutines;
    int runningCoroutines = 0;
    const int maxConcurrency = std::thread::hardware_concurrency();
};

Scheduler<> scheduler;

struct Task {
    std::coroutine_handle<> coro;
};

void worker(Task task) {
    scheduler.push(task.coro);
}

int main() {
    std::vector<Task> tasks;
    for (int i = 0; i < 10; ++i) {
        tasks.push_back({[i]() -> std::coroutine_handle<> {
            std::cout << "Task " << i << " is running on thread " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
            return std::coroutine_handle<>::from_address(this);
        }});
    }
    std::vector<std::jthread> threads;
    for (auto& task : tasks) {
        threads.push_back(std::jthread(worker, task));
    }
    for (auto& thread : threads) {
        thread.join();
    }
    return 0;
}

在这个例子中,我们实现了一个简单的协程调度器,它可以控制并发执行的协程数量,并在达到最大并发数时将剩余的协程放入等待队列。通过使用协程调度器,我们可以有效地管理协程的执行,提高程序的性能和响应能力。

协程池

协程池是一种管理协程资源的容器,它可以复用协程对象,减少创建和销毁协程的开销。在C++中,我们可以使用std::coroutine_handlestd::jthread来实现协程池。

由于篇幅限制,这里不再详细展开协程池的实现。但基本思路是为每个任务创建一个协程,并将这些协程存储在一个池中。当需要执行任务时,从池中取出一个空闲的协程并执行它;当任务完成时,将协程放回池中以供后续使用。

通道(Channels)

通道是一种用于在不同协程之间传递数据的机制。在C++中,我们可以使用std::queuestd::condition_variable来实现一个简单的通道。

#include <condition_variable>
#include <coroutine>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>

template<typename T>
class Channel {
public:
    T receive() {
        std::unique_lock<std::mutex> lock(mtx);
        cond.wait(lock, [this] { return !buffer.empty() || done; });
        if (done) throw std::runtime_error("Channel closed");
        T value = buffer.front();
        buffer.pop();
        return value;
    }

    void send(T value) {
        std::unique_lock<std::mutex> lock(mtx);
        buffer.push(value);
        lock.unlock();
        cond.notify_one();
    }

    void close() {
        std::unique_lock<std::mutex> lock(mtx);
        done = true;
        lock.unlock();
        cond.notify_all();
    }

private:
    std::queue<T> buffer;
    std::mutex mtx;
    std::condition_variable cond;
    bool done = false;
};

Channel<int> channel;

struct Producer {
    std::coroutine_handle<> coro;
};

Producer producer(int n) {
    for (int i = 0; i < n; ++i) {
        channel.send(i);
        co_yield;
    }
}

struct Consumer {
    std::coroutine_handle<> coro;
};

Consumer consumer() {
    for (int i = 0; i < 10; ++i) {
        int value = channel.receive();
        std::cout << "Received: " << value << std::endl;
        co_yield;
    }
}

int main() {
    auto producerCoro = producer(10);
    auto consumerCoro = consumer();
    std::jthread producerThread(producerCoro);
    std::jthread consumerThread(consumerCoro);
    producerThread.join();
    consumerThread.join();
    channel.close();
    return 0;
}

在这个例子中,我们实现了一个简单的通道类,它可以在不同的协程之间传递数据。生产者协程通过send方法发送数据到通道,消费者协程通过receive方法从通道接收数据。通过使用通道,我们可以在不同的协程之间安全地传递数据,而无需担心数据竞争和同步问题。

结语:

随着C++20的发布和普及,协程已经成为了现代C++编程中不可或缺的一部分。在本篇博客中,我们深入探讨了C++中协程的基本概念、实现原理以及高级应用案例,包括生成器、异步任务、协程调度器、协程池和通道等。通过这些高级特性,我们可以编写出更加简洁、高效和可维护的并发和异步代码。

协程不仅为我们提供了强大的工具来处理复杂的编程问题,还极大地提高了代码的可读性和可维护性。它们使得编写高并发的程序变得更加简单,允许开发者将注意力集中在业务逻辑上,而不是底层的线程管理和同步细节。

尽管协程带来了许多好处,但它们也引入了新的挑战和复杂性。正确地使用和管理协程资源,确保程序的正确性和性能,是每个使用协程的开发者需要面对的问题。因此,深入学习和理解协程的原理和最佳实践,对于任何希望在现代C++开发中保持竞争力的程序员来说都是至关重要的。

在未来,随着C++标准的不断发展和改进,我们可以期待协程和其他异步编程工具将变得更加强大和易用。作为开发者,我们应该持续关注这些新技术的发展,不断学习和实践,以便能够充分利用它们来构建更加出色的软件。

感谢您阅读这篇博客,希望它能够为您提供有价值的见解,并激发您对C++协程的兴趣和探索。

  • 43
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给你一颗语法糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值