当前位置: 首页 > news >正文

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

本文是我C++学习之旅系列的第五十篇技术文章,也是第三阶段"现代C++特性"的第十二篇,继续介绍C++20引入的新特性,本篇重点是协程(Coroutines)。查看完整系列目录了解更多内容。

引言

在现代软件开发中,异步编程已经成为处理I/O操作、并发任务和事件驱动系统的关键范式。然而,传统的基于回调或基于Future/Promise的异步编程模型常常导致代码复杂、难以维护,甚至出现所谓的"回调地狱"。C++20引入的协程(Coroutines)提供了一种革命性的方法来简化异步编程,使开发者能够以看似同步的方式编写异步代码。

协程是一种可以暂停执行并在之后恢复的函数。与普通函数不同,协程可以在执行过程中让出控制权,并在适当的时机重新获得控制权继续执行。这种能力使得协程特别适合处理异步操作、生成器模式和其他需要控制流灵活转移的场景。

本文将介绍C++20协程的基本概念、工作原理、核心组件以及实际应用场景,帮助你掌握这一强大的新特性,编写更简洁、更高效的异步代码。

目录

  • 协程基础概念
  • 协程的语法与组件
  • 协程的工作原理
  • 构建协程类型
  • 实际应用场景
  • 与现有库的集成
  • 性能考量
  • 最佳实践与陷阱
  • 总结

协程基础概念

什么是协程

协程(Coroutine)是一种特殊的函数,能够在执行过程中暂停并保存当前状态,稍后再从暂停的位置继续执行。这种能力使协程成为处理异步操作的强大工具。

协程的核心特点:

  1. 可暂停执行:协程可以在指定点暂停执行,并让出控制权
  2. 状态保存:暂停时,协程的执行状态(包括局部变量和执行位置)被保存
  3. 可恢复执行:协程可以从上次暂停的位置恢复执行
  4. 多入口多出口:与传统函数的"单入口单出口"不同,协程可以有多个出口(暂停点)和入口(恢复点)

下面是一个简单的协程概念示例(使用伪代码):

协程 generate_sequence() {yield 1;  // 产生值1并暂停yield 2;  // 恢复后产生值2并再次暂停yield 3;  // 恢复后产生值3并再次暂停return;   // 结束协程
}主函数() {generator = generate_sequence();value1 = generator.next();  // 获取1value2 = generator.next();  // 获取2value3 = generator.next();  // 获取3
}

协程与线程的区别

协程和线程都是实现并发的机制,但它们有根本性的区别:

特性协程线程
调度方式协作式调度(自己决定何时让出控制权)抢占式调度(由操作系统调度)
切换开销非常低(通常只涉及少量寄存器)较高(涉及完整的上下文切换)
存储空间共享同一线程的栈空间每个线程有独立的栈空间
并行执行同一时刻只有一个协程在执行(单线程内)可以真正并行执行(多核处理器上)
同步机制通常不需要复杂的同步原语需要互斥锁、条件变量等同步机制

协程的主要优势:

  • 更轻量级,创建和销毁成本低
  • 切换开销小,适合频繁切换的场景
  • 不需要考虑大多数并发问题,简化编程模型
  • 特别适合I/O密集型应用,可大幅提高性能

C++20协程的特点

C++20引入的协程具有以下特点:

  1. 低级机制:C++20提供的是协程的底层基础设施,而非高级抽象
  2. 编译器支持:依赖编译器支持,将协程函数转换为状态机
  3. 可定制性:高度可定制,允许库开发者构建各种高级协程抽象
  4. 零开销抽象:设计目标是提供零开销抽象,不为不使用的特性付出代价
  5. 无栈协程:C++20使用的是无栈协程模型,协程状态存储在堆上而非独立栈上

C++20协程的关键语法元素:

  • co_await:暂停协程执行,等待某个操作完成
  • co_yield:产生一个值并暂停执行
  • co_return:完成协程执行并返回一个值

一个协程在C++20中的标志是使用了以上任一关键字。

协程的语法与组件

co_await表达式

co_await是C++20协程的核心操作,用于暂停协程执行,等待某个操作完成,然后恢复执行:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>// 一个简单的可等待对象
struct SimpleAwaiter {bool await_ready() const noexcept {return false;  // 表示需要暂停协程}void await_suspend(std::coroutine_handle<> handle) const noexcept {// 模拟异步操作,2秒后恢复协程std::thread([handle]() {std::this_thread::sleep_for(std::chrono::seconds(2));handle.resume();  // 恢复协程执行}).detach();}int await_resume() const noexcept {return 42;  // 返回给co_await表达式的结果}
};// 一个简单的协程返回类型
struct SimpleTask {struct promise_type {SimpleTask get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};
};// 使用co_await的简单协程
SimpleTask simple_coroutine() {std::cout << "协程开始执行" << std::endl;std::cout << "协程即将暂停..." << std::endl;int result = co_await SimpleAwaiter{};  // 在这里暂停协程std::cout << "协程恢复执行,收到结果: " << result << std::endl;std::cout << "协程执行完毕" << std::endl;
}

co_await表达式的处理流程:

  1. 调用等待对象的await_ready()方法
  2. 如果返回true,直接继续执行协程
  3. 如果返回false,暂停协程执行并调用等待对象的await_suspend(handle)方法
  4. 当协程需要恢复时,调用协程句柄的resume()方法
  5. 协程恢复后,调用等待对象的await_resume()方法获取结果

co_yield表达式

co_yield用于从协程产生值并暂停执行,特别适用于实现生成器模式:

#include <iostream>
#include <coroutine>// 简化的生成器实现
template<typename T>
class Generator {
public:struct promise_type {T value;Generator get_return_object() {return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }std::suspend_always yield_value(T val) {value = val;return {};}void return_void() {}void unhandled_exception() { std::terminate(); }};// 生成器方法bool next() {if (handle && !handle.done()) {handle.resume();return !handle.done();}return false;}T value() const { return handle.promise().value; }// 资源管理~Generator() { if (handle) handle.destroy(); }Generator(Generator&& other) noexcept : handle(other.handle) { other.handle = {}; }Generator& operator=(Generator&&) = delete;Generator(const Generator&) = delete;private:explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 使用co_yield的生成器协程
Generator<int> fibonacci(int n) {if (n > 0) co_yield 0;if (n > 1) co_yield 1;int a = 0, b = 1;for (int i = 2; i < n; ++i) {int next = a + b;co_yield next;a = b;b = next;}
}

co_yield表达式在底层被转换为co_await表达式:

  • co_yield x 基本等同于 co_await promise.yield_value(x)
  • 允许协程产生一个值并暂停,等待下一次恢复再继续执行

co_return语句

co_return用于结束协程执行并指定返回值:

#include <iostream>
#include <coroutine>
#include <utility>// 简单的返回值协程
template<typename T>
class Task {
public:struct promise_type {T result;Task get_return_object() {return Task{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_never initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void return_value(T value) {result = std::move(value);}void unhandled_exception() { std::terminate(); }};T result() const { return handle.promise().result; }bool is_ready() const { return handle && handle.done(); }// 资源管理~Task() { if (handle) handle.destroy(); }// ... 其他移动构造等private:explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 使用co_return的协程
Task<std::string> compute_greeting(std::string name) {std::string greeting = "Hello, " + name + "!";co_return greeting;  // 结束协程并返回结果
}

co_return的行为取决于Promise类型的实现:

  • co_return; (无值):调用 promise.return_void()
  • co_return x; (有值):调用 promise.return_value(x)
  • 之后协程执行进入最终阶段,调用 co_await promise.final_suspend()

Promise对象

Promise对象是C++20协程的核心组件,用于控制协程的行为。每个协程都关联一个Promise对象,由协程框架在协程创建时构造:

template<typename ReturnType>
struct MyPromise {// 必需的成员ReturnType get_return_object();  // 创建并返回协程的返回对象std::suspend_always initial_suspend();  // 控制协程开始时是否立即执行std::suspend_always final_suspend() noexcept;  // 控制协程结束时的行为// 与返回值相关的成员(二选一)void return_void();  // 处理无返回值的co_return语句void return_value(ReturnType value);  // 处理有返回值的co_return语句// 异常处理成员void unhandled_exception();  // 处理协程中未捕获的异常// 可选的其他成员std::suspend_always yield_value(ValueType value);  // 处理co_yield表达式AwaitableType await_transform(OtherAwaitable a);  // 自定义co_await行为
};

Promise对象的主要职责:

  1. 创建协程的返回对象
  2. 控制协程的初始化和最终化行为
  3. 处理协程的返回值
  4. 处理协程中的异常
  5. 处理co_yield表达式
  6. 自定义co_await表达式的行为

协程句柄

协程句柄(std::coroutine_handle<>)是用于操作协程的非拥有者句柄,提供了与协程交互的接口:

struct MyPromise {struct Task {std::coroutine_handle<MyPromise> handle;~Task() {if (handle) handle.destroy();  // 必须手动销毁协程}};Task get_return_object() {return Task{std::coroutine_handle<MyPromise>::from_promise(*this)};}// ... 其他必需方法
};// 协程句柄操作示例
auto task = some_coroutine();
std::coroutine_handle<MyPromise> handle = task.handle;if (!handle.done()) {handle.resume();  // 恢复协程执行
}// 检查协程是否完成
bool finished = handle.done();

协程句柄的主要操作:

  1. resume():恢复协程执行
  2. done():检查协程是否执行完毕
  3. destroy():销毁协程帧,释放资源
  4. promise():访问关联的Promise对象
  5. address():获取协程帧的地址

重要注意事项:

  • 协程句柄不会自动销毁协程,必须手动调用destroy()
  • 对已完成或已销毁的协程调用resume()会导致未定义行为

协程的工作原理

协程状态

协程在执行过程中可能处于以下几种状态:

  1. 挂起状态(Suspended):协程暂停执行,等待被恢复
  2. 活动状态(Active):协程正在执行中
  3. 完成状态(Done):协程已执行完毕

状态转换:

  • 创建后→初始挂起(根据initial_suspend决定)
  • 挂起→活动:通过resume()恢复执行
  • 活动→挂起:通过co_awaitco_yield暂停
  • 活动→完成:通过co_return或执行到函数末尾
  • 完成→最终挂起(根据final_suspend决定)

协程帧

协程帧是存储协程状态的内存区域,包含以下内容:

  1. Promise对象:控制协程行为的对象
  2. 参数副本:协程参数的拷贝或引用
  3. 局部变量:协程中声明的局部变量
  4. 暂停点信息:当前执行位置的信息
  5. 恢复后要执行的代码地址:恢复执行时的下一条指令地址

协程帧的生命周期:

  1. 协程首次调用时在堆上分配
  2. 协程执行期间持续存在,即使协程暂停
  3. 调用coroutine_handle::destroy()时释放

挂起与恢复机制

协程挂起过程:

  1. 当执行到co_await expr时,首先计算表达式expr得到等待者对象
  2. 调用等待者的await_ready()方法检查是否需要挂起
  3. 如果返回false,保存当前执行状态
  4. 调用等待者的await_suspend(handle)方法,可能安排后续恢复操作
  5. 让出控制权,返回到协程的调用者

协程恢复过程:

  1. 通过协程句柄的resume()方法恢复执行
  2. 恢复协程的执行状态(包括局部变量等)
  3. 调用等待者的await_resume()方法获取结果值
  4. 继续执行剩余的协程代码,直到下一个挂起点或结束

协程的生命周期

一个典型的协程生命周期包括以下阶段:

  1. 创建阶段

    • 分配协程帧
    • 构造Promise对象
    • 调用promise.get_return_object()获取返回对象
    • 调用co_await promise.initial_suspend()决定是否立即挂起
  2. 执行阶段

    • 执行协程函数体
    • co_awaitco_yield点暂停和恢复
    • 处理各种操作和计算
  3. 完成阶段

    • 执行co_return语句或到达函数末尾
    • 调用promise.return_void()promise.return_value(x)
    • 调用co_await promise.final_suspend()决定最终挂起行为
  4. 销毁阶段

    • 调用coroutine_handle::destroy()销毁协程帧
    • 释放所有资源

构建协程类型

基础协程返回类型

创建一个自定义的协程返回类型通常包括以下步骤:

  1. 定义返回类型(比如Task<T>
  2. 定义该类型的promise_type内部类
  3. 实现必要的Promise方法
  4. 提供协程句柄管理机制

下面是一个简化但完整的异步任务类型实现:

template<typename T = void>
class Task {
public:// Promise类型struct promise_type {std::variant<std::monostate, T, std::exception_ptr> result;std::coroutine_handle<> continuation = nullptr;Task get_return_object() {return Task{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }auto final_suspend() noexcept {struct FinalAwaiter {bool await_ready() noexcept { return false; }std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h) noexcept {// 如果有等待的协程,恢复它if (auto cont = h.promise().continuation)return cont;return std::noop_coroutine();}void await_resume() noexcept {}};return FinalAwaiter{};}template<typename U>void return_value(U&& value) {result.template emplace<1>(std::forward<U>(value));}void unhandled_exception() {result.template emplace<2>(std::current_exception());}};// 等待器auto operator co_await() const noexcept {struct TaskAwaiter {std::coroutine_handle<promise_type> handle;bool await_ready() const noexcept { return handle.done(); }std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {handle.promise().continuation = h;return handle;}T await_resume() {auto& result = handle.promise().result;if (result.index() == 2)std::rethrow_exception(std::get<2>(result));if constexpr (!std::is_void_v<T>)return std::get<1>(std::move(result));}};return TaskAwaiter{handle};}// 手动控制方法void start() {if (handle && !handle.done())handle.resume();}bool is_done() const { return !handle || handle.done(); }// 资源管理~Task() { if (handle) handle.destroy(); }Task(Task&& other) noexcept : handle(other.handle) { other.handle = nullptr; }Task& operator=(Task&&) noexcept;  // 实现省略Task(const Task&) = delete;Task& operator=(const Task&) = delete;private:explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 示例协程函数
Task<int> compute() {co_return 42;
}Task<> use_value(int value) {std::cout << "值: " << value << std::endl;co_return;
}Task<> example() {int result = co_await compute();co_await use_value(result);
}

实现生成器

生成器是协程的一个常见应用,用于惰性生成一系列值。以下是一个生成器实现的关键部分:

template<typename T>
class Generator {
public:struct promise_type {std::optional<T> current_value;std::exception_ptr exception;Generator get_return_object() {return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }std::suspend_always yield_value(T value) {current_value = std::move(value);return {};}void return_void() {}void unhandled_exception() { exception = std::current_exception(); }};// 迭代方法bool next() {if (handle && !handle.done()) {handle.resume();if (handle.done()) return false;return true;}return false;}T value() const { return *handle.promise().current_value; }// 迭代器支持,使生成器可用于范围for循环class iterator { /* 实现省略 */ };iterator begin();iterator end();// 资源管理~Generator() { if (handle) handle.destroy(); }// ... 移动构造等private:explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}std::coroutine_handle<promise_type> handle;
};// 示例:斐波那契数列生成器
Generator<int> fibonacci(int n) {if (n > 0) co_yield 0;if (n > 1) co_yield 1;int a = 0, b = 1;for (int i = 2; i < n; ++i) {int next = a + b;co_yield next;a = b;b = next;}
}// 使用方式
void use_generator() {for (int value : fibonacci(10)) {std::cout << value << " ";  // 输出: 0 1 1 2 3 5 8 13 21 34}
}

自定义等待者

等待者(Awaiter)是实现co_await运算符行为的对象,必须提供三个特定方法:

// 简单的延迟等待器
class Delay {
public:explicit Delay(std::chrono::milliseconds duration) : duration_(duration) {}bool await_ready() const noexcept {return duration_.count() <= 0;}void await_suspend(std::coroutine_handle<> handle) const {std::thread([handle, this]() {std::this_thread::sleep_for(duration_);handle.resume();}).detach();}void await_resume() const noexcept {}private:std::chrono::milliseconds duration_;
};// 使用延迟等待器
Task<> delay_example() {std::cout << "开始" << std::endl;co_await Delay{std::chrono::seconds(1)};std::cout << "1秒后" << std::endl;co_await Delay{std::chrono::milliseconds(500)};std::cout << "再过0.5秒后" << std::endl;
}

自定义等待者需要实现的三个方法:

  1. await_ready(): 决定是否需要暂停协程
  2. await_suspend(handle): 安排何时恢复协程
  3. await_resume(): 提供co_await表达式的结果值

实际应用场景

异步I/O操作

协程特别适合处理异步I/O操作,简化传统的回调模式:

// 模拟异步文件操作
class AsyncFile {
public:AsyncFile(const std::string& filename) : filename_(filename) {std::cout << "打开文件: " << filename << std::endl;}~AsyncFile() {std::cout << "关闭文件: " << filename_ << std::endl;}// 异步读取操作的等待者class ReadOperation {public:ReadOperation(const std::string& filename, int size) : filename_(filename), size_(size) {}bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {// 模拟异步I/O操作std::thread([this, handle]() {std::cout << "异步读取文件: " << filename_ << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(500));// 模拟读取data_ = std::string(size_, 'D');handle.resume();}).detach();}std::string await_resume() { return data_; }private:std::string filename_;int size_;std::string data_;};ReadOperation read(int size) {return ReadOperation(filename_, size);}// 写入操作类似...private:std::string filename_;
};// 异步文件处理协程
Task<> process_file(const std::string& input_file, const std::string& output_file) {try {AsyncFile input(input_file);AsyncFile output(output_file);// 异步读取std::string data = co_await input.read(1024);std::cout << "读取完成,数据大小: " << data.size() << " 字节" << std::endl;// 处理数据std::string processed_data = "处理后: " + data;// 异步写入co_await output.write(processed_data);std::cout << "写入完成" << std::endl;} catch (const std::exception& e) {std::cerr << "错误: " << e.what() << std::endl;}
}

协程在异步I/O中的优势:

  1. 使异步代码看起来像同步代码
  2. 自然的错误处理机制
  3. 资源的自动管理
  4. 避免回调嵌套

并发任务控制

协程可以用于简化并发任务的控制和协调:

// 并发任务执行器
class TaskExecutor {
public:// 提交任务并返回awaitertemplate<typename Func>auto submit(Func&& func) {struct Awaiter {Func func;bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {std::thread([this, handle]() {try {result_ = func();} catch (...) {exception_ = std::current_exception();}handle.resume();}).detach();}auto await_resume() {if (exception_)std::rethrow_exception(exception_);return result_;}std::optional<std::invoke_result_t<Func>> result_;std::exception_ptr exception_;};return Awaiter{std::forward<Func>(func)};}
};// 使用执行器
Task<> parallel_tasks() {TaskExecutor executor;auto task1 = executor.submit([]() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;});auto task2 = executor.submit([]() {std::this_thread::sleep_for(std::chrono::seconds(2));return std::string("Hello");});// 并发等待两个任务int result1 = co_await task1;std::string result2 = co_await task2;std::cout << "结果: " << result1 << ", " << result2 << std::endl;
}

生成器与惰性计算

协程特别适合实现惰性计算的生成器:

Generator<int> range(int start, int end, int step = 1) {for (int i = start; i < end; i += step) {co_yield i;}
}Generator<std::string> file_lines(const std::string& filename) {std::ifstream file(filename);std::string line;while (std::getline(file, line)) {co_yield line;}
}// 无限序列,按需计算
Generator<int> primes() {std::vector<int> found_primes;co_yield 2;  // 第一个质数for (int n = 3; ; n += 2) {  // 从3开始的奇数bool is_prime = true;for (int prime : found_primes) {if (prime * prime > n) break;  // 优化检查if (n % prime == 0) {is_prime = false;break;}}if (is_prime) {found_primes.push_back(n);co_yield n;}}
}// 使用生成器
void use_generators() {// 使用范围生成器for (int i : range(0, 10, 2)) {std::cout << i << " ";  // 输出 0 2 4 6 8}// 读取文件的前10行int count = 0;for (const auto& line : file_lines("data.txt")) {std::cout << line << std::endl;if (++count >= 10) break;}// 获取前10个质数count = 0;for (int p : primes()) {std::cout << p << " ";if (++count >= 10) break;}
}

状态机实现

协程可以优雅地实现状态机:

// 解析器状态机
Task<std::vector<std::string>> parse_csv(std::string_view data) {std::vector<std::string> fields;std::string current_field;enum class State { Field, QuotedField, QuoteInQuotedField };State state = State::Field;for (char c : data) {// 复杂状态处理逻辑switch (state) {case State::Field:if (c == ',') {fields.push_back(current_field);current_field.clear();co_await Delay{std::chrono::microseconds(1)};  // 允许状态检查} else if (c == '"') {state = State::QuotedField;} else {current_field += c;}break;case State::QuotedField:if (c == '"') {state = State::QuoteInQuotedField;} else {current_field += c;}break;case State::QuoteInQuotedField:if (c == '"') {current_field += '"';state = State::QuotedField;} else if (c == ',') {fields.push_back(current_field);current_field.clear();state = State::Field;co_await Delay{std::chrono::microseconds(1)};  // 允许状态检查} else {current_field += c;state = State::Field;}break;}}// 添加最后一个字段fields.push_back(current_field);co_return fields;
}

与现有库的集成

与std::future的集成

可以将协程与std::future集成,简化异步操作:

// 使future可等待
template<typename T>
auto operator co_await(std::future<T>&& future) {struct FutureAwaiter {std::future<T> future;bool await_ready() const {return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;}void await_suspend(std::coroutine_handle<> handle) {std::thread([this, handle]() mutable {future.wait();handle.resume();}).detach();}T await_resume() {return future.get();}};return FutureAwaiter{std::move(future)};
}// 示例:使用std::async与协程
Task<> future_example() {auto future = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;});std::cout << "等待future..." << std::endl;int result = co_await std::move(future);std::cout << "结果: " << result << std::endl;
}

与异步网络库的结合

协程可以与异步网络库(如Asio)结合,简化网络编程:

// 简化的Asio协程适配器
template<typename R>
auto awaitable(std::function<void(std::function<void(R)>)> async_op) {struct Awaiter {std::function<void(std::function<void(R)>)> async_op;R result;bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {async_op([this, handle](R r) {result = std::move(r);handle.resume();});}R await_resume() { return std::move(result); }};return Awaiter{std::move(async_op)};
}// 假设的异步HTTP客户端
class HttpClient {
public:using Callback = std::function<void(std::string)>;// 异步HTTP GET请求void async_get(const std::string& url, Callback callback) {// 模拟异步HTTP请求std::thread([url, callback = std::move(callback)]() {std::this_thread::sleep_for(std::chrono::seconds(1));std::string response = "Response from " + url;callback(response);}).detach();}
};// 使用协程简化HTTP请求
Task<> fetch_data() {HttpClient client;std::cout << "发起请求..." << std::endl;// 转换为可等待操作std::string response = co_await awaitable<std::string>([&client](auto cb) {client.async_get("https://example.com", std::move(cb));});std::cout << "收到响应: " << response << std::endl;
}

与事件循环的结合

协程可以集成到事件循环中,实现高效的非阻塞异步编程:

// 简化的事件循环
class EventLoop {
public:using Task = std::function<void()>;void post(Task task) {std::lock_guard<std::mutex> lock(mutex_);tasks_.push(std::move(task));has_task_.notify_one();}void run() {while (!stop_) {Task task;{std::unique_lock<std::mutex> lock(mutex_);has_task_.wait(lock, [this] { return !tasks_.empty() || stop_; });if (stop_) break;task = std::move(tasks_.front());tasks_.pop();}task();}}void stop() {std::lock_guard<std::mutex> lock(mutex_);stop_ = true;has_task_.notify_one();}private:std::queue<Task> tasks_;std::mutex mutex_;std::condition_variable has_task_;bool stop_ = false;
};// 事件循环协程适配器
class EventLoopAwaiter {
public:EventLoopAwaiter(EventLoop& loop) : loop_(loop) {}bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<> handle) {loop_.post([handle]() { handle.resume(); });}void await_resume() {}private:EventLoop& loop_;
};// 使用事件循环执行协程
Task<> event_loop_example(EventLoop& loop) {std::cout << "开始" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第一次在事件循环中恢复" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第二次在事件循环中恢复" << std::endl;co_await EventLoopAwaiter(loop);std::cout << "第三次在事件循环中恢复" << std::endl;
}

性能考量

协程的开销

协程相比传统函数有一些额外开销:

  1. 内存分配:协程帧通常在堆上分配,虽然编译器可能优化某些情况
  2. 额外的函数调用:如await_readyawait_suspend等方法的调用
  3. 状态管理:保存和恢复协程状态的开销

但协程通常比其他异步编程方式(如线程)的开销要小得多。

零开销抽象的实现

C++20协程设计为"零开销抽象",遵循C++的"只为你使用的部分付费"原则:

  1. 未使用的协程特性不会产生开销
  2. 编译器可以对协程进行优化,例如:
    • 内联协程函数以减少调用开销
    • 在某些情况下避免堆分配
    • 消除不必要的暂停/恢复操作

优化策略

优化协程性能的几种策略:

  1. 避免不必要的暂停点:比如使用await_ready()返回true避免不必要的暂停

  2. 减少内存分配

    // 针对小协程帧使用固定大小的内存池
    void* operator new(size_t size) {static thread_local std::array<byte, 1024> buffer;static thread_local size_t used = 0;if (size + used <= buffer.size()) {void* result = buffer.data() + used;used += size;return result;}return ::operator new(size);
    }
    
  3. 尽可能使用值类型而非引用计数智能指针,减少动态分配和引用计数操作

  4. 避免协程嵌套过深,每一层嵌套都会创建新的协程帧

最佳实践与陷阱

错误处理

协程中的错误处理策略:

  1. 使用标准的try-catch机制

    Task<> error_handling_example() {try {int result = co_await potentially_throwing_operation();std::cout << "结果: " << result << std::endl;} catch (const std::exception& e) {std::cerr << "捕获到异常: " << e.what() << std::endl;}
    }
    
  2. 通过Promise对象的unhandled_exception方法传播异常

    void unhandled_exception() {exception_ = std::current_exception();
    }
    
  3. 使用错误码和std::expected代替异常,适合性能关键场景

取消操作

实现协程操作的取消机制:

class CancellationToken {
public:bool is_cancelled() const {return cancelled_.load(std::memory_order_acquire);}void cancel() {cancelled_.store(true, std::memory_order_release);}private:std::atomic<bool> cancelled_{false};
};// 在协程中使用取消令牌
Task<> cancellable_operation(std::shared_ptr<CancellationToken> token) {for (int i = 0; i < 10; ++i) {if (token->is_cancelled()) {std::cout << "操作被取消" << std::endl;co_return;}std::cout << "步骤 " << i << std::endl;co_await Delay{std::chrono::milliseconds(500)};}
}

调试协程

调试协程的挑战与技巧:

  1. 打印生命周期事件,跟踪协程的创建、暂停、恢复和销毁
  2. 使用协程上下文信息,在每个暂停点记录状态
  3. 在Promise类型中添加调试辅助方法

例如,添加调试日志:

class DebugPromise {
public:// ... 正常Promise方法auto initial_suspend() {std::cout << "协程开始[" << id_ << "]" << std::endl;return std::suspend_always{};}auto final_suspend() noexcept {std::cout << "协程结束[" << id_ << "]" << std::endl;return std::suspend_always{};}private:static inline std::atomic<int> next_id_ = 0;int id_ = next_id_++;
};

避免的常见错误

使用协程时的常见陷阱:

  1. 协程句柄生命周期管理不当

    // 错误:协程句柄被销毁,但协程尚未完成
    void misuse_handle() {auto handle = some_coroutine().handle;handle.resume();handle.destroy();  // 如果协程还在运行,会导致未定义行为
    }
    
  2. 循环依赖导致内存泄漏:两个协程互相等待对方完成

  3. 忽略异常传播:异常如果未被处理,可能会导致程序终止

  4. 没有处理协程挂起时的资源释放问题

    Task<> resource_leak() {auto resource = acquire_resource();  // 获取资源co_await some_operation();  // 如果挂起,resource可能未被正确释放release_resource(resource);
    }// 更好的方式:使用RAII
    Task<> proper_cleanup() {auto guard = ResourceGuard();  // RAII包装器co_await some_operation();  // 即使挂起,guard的析构函数也会在协程销毁时被调用
    }
    

总结

C++20协程是现代C++中最具革命性的特性之一,它为异步编程提供了一种优雅而高效的解决方案。协程允许我们以看似同步的方式编写异步代码,大大提高了代码的可读性、可维护性和效率。

主要优势包括:

  1. 简化异步编程:使异步代码的结构与同步代码类似,避免回调地狱
  2. 高效的状态管理:无栈协程模型提供轻量级的执行控制转移
  3. 强大的抽象能力:可以封装各种异步操作为协程
  4. 与现有C++特性兼容:与异常处理、RAII、智能指针等特性无缝集成
  5. 灵活的定制性:提供低级机制,允许构建各种高级抽象

协程特别适合以下场景:

  • 异步I/O操作
  • 并发任务控制
  • 生成器与惰性计算
  • 状态机实现
  • 事件驱动编程

虽然C++20协程的使用有一定的学习曲线,但一旦掌握,它将成为我们处理异步编程问题的强大工具。随着越来越多的库和框架提供协程支持,C++协程将在未来的软件开发中发挥越来越重要的作用。

在下一篇文章中,我们将探讨C++20的另一个重要特性:范围(Ranges)库,它如何革新C++的算法和容器交互方式。


这是我C++学习之旅系列的第五十篇技术文章。查看完整系列目录了解更多内容。

相关文章:

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

C学习&#xff1a;六个月从基础到就业——C20&#xff1a;协程(Coroutines) 本文是我C学习之旅系列的第五十篇技术文章&#xff0c;也是第三阶段"现代C特性"的第十二篇&#xff0c;继续介绍C20引入的新特性&#xff0c;本篇重点是协程(Coroutines)。查看完整系列目录…...

【DAY22】 复习日

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 仔细回顾一下之前21天的内容 作业&#xff1a; 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 kaggle泰坦里克号人员生还预测...

tauri2项目使用sidcar嵌入可执行文件并使用命令行调用

Sidecar 是 Tauri 框架中的一个功能&#xff0c;允许你将现有的命令行程序&#xff08;CLI&#xff09;打包并分发到你的 Tauri 应用程序中。以下是它的主要作用和用法。集成命令行工具&#xff1a;将现有的 CLI 程序无缝集成到你的 Tauri 应用中。跨平台分发&#xff1a;确保你…...

选择合适的AI模型:解析Trae编辑器中的多款模型及其应用场景

在当今数字化时代&#xff0c;人工智能技术飞速发展&#xff0c;各种AI模型层出不穷&#xff0c;为人们的工作和生活带来了极大的便利。Trae编辑器作为一款集成了多种先进AI模型的工具&#xff0c;为用户提供了丰富的选择&#xff0c;以满足不同场景下的多样化需求。本文将深入…...

超越想象:利用MetaGPT打造高效的AI协作环境

前言 在人工智能迅速发展的今天&#xff0c;如何让多个大语言模型&#xff08;LLM&#xff09;高效协同工作成为关键挑战。MetaGPT 作为一种创新的多智能体框架&#xff0c;成功模拟了一个真实软件公司的运作流程&#xff0c;实现了从需求分析到代码实现的全流程自动化&#x…...

BOM知识点

BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;是用于访问和操作浏览器窗口的编程接口。以下是一些BOM的知识点总结&#xff1a; 核心对象 • window&#xff1a;BOM的核心对象&#xff0c;代表浏览器窗口。它也是全局对象&#xff0c;所有全…...

IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink

文章目录 概述LiteOS Studio不推荐&#xff1f;安装和使用手册呢?HCIP实验的源码呢&#xff1f; 软件和依赖安装软件下载软件安装插件安装依赖工具-方案2依赖工具-方案1 工程配置打开或新建工程板卡配置组件配置编译器配置-gcc工具链编译器配置-Makefile脚本其他配置编译完成 …...

常见的 HTTP 接口(请求方法)

一&#xff1a;GET 作用&#xff1a;从服务器获取资源&#xff08;查询数据&#xff09;。特点&#xff1a; 请求参数通过 URL 传递&#xff08;如https://api.example.com/users?id123&#xff09;&#xff0c;参数会显示在地址栏中。不修改服务器数据&#xff0c;属于幂等操…...

墨水屏显示模拟器程序解读

程序如下&#xff1a;出处https://github.com/tsl0922/EPD-nRF5?tabreadme-ov-file // GUI emulator for Windows // This code is a simple Windows GUI application that emulates the display of an e-paper device. #include <windows.h> #include <stdint.h>…...

【图像生成大模型】Step-Video-T2V:下一代文本到视频生成技术

Step-Video-T2V&#xff1a;下一代文本到视频生成技术 引言Step-Video-T2V 项目概述核心技术1. 视频变分自编码器&#xff08;Video-VAE&#xff09;2. 3D 全注意力扩散 Transformer&#xff08;DiT w/ 3D Full Attention&#xff09;3. 视频直接偏好优化&#xff08;Video-DPO…...

【Java学习笔记】【第一阶段项目实践】房屋出租系统(面向对象版本)

房屋出租系统&#xff08;面向对象版本&#xff09; 整体思想&#xff1a;采用数组存储房屋信息&#xff0c;深刻体会面向对象的好处和过程 一、实现需求 &#xff08;1&#xff09;用户层 系统菜单显示 提示用户输入对应的数字选择功能 各个功能界面操作提示&#xff08;底…...

18. 结合Selenium和YAML对页面继承对象PO的改造

18. 结合Selenium和YAML对页面继承对象PO的改造 一、架构改造核心思路 1.1 改造前后对比 #mermaid-svg-ziagMhNLS5fIFWrx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ziagMhNLS5fIFWrx .error-icon{fill:#5522…...

Vue-监听属性

监听属性 简单监听 点击切换名字&#xff0c;来回变更Tom/Jerry&#xff0c;输出 你好&#xff0c;Tom/Jerry 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>监听属性</title><!-- …...

AI写PPT可以用吗?我测试了3款AI写PPT工具,分享感受

上周五临下班&#xff0c;领导突然让我周末赶出一份季度营销报告 PPT&#xff0c;还要求周一晨会展示。看着空荡荡的 PPT 页面&#xff0c;我满心都是绝望 —— 周末不仅泡汤&#xff0c;搞不好还得熬夜到凌晨。好在同部门的前辈给我推荐了几款 AI 写 PPT 工具&#xff0c;没想…...

FreeSWITCH 简单图形化界面43 - 使用百度的unimrcp搞个智能话务台,用的在线的ASR和TTS

FreeSWITCH 简单图形化界面43 - 使用百度的unimrcp搞个智能话务台 0、一个fs的web配置界面预览1、安装unimrcp模块2、安装完成后&#xff0c;配置FreeSWITCH。2.1 有界面的配置2.1.1 mod_unimrcp模块配置2.1.2 mod_unimrcp客户端配置 2.2 无界面的配置 3、呼叫规则4、编写流程4…...

C 语言学习笔记(函数)

内容提要 函数 函数的概述函数的分类函数的定义形参和实参函数的返回值 函数 函数的概述 **函数&#xff1a;**实现一定功能的&#xff0c;独立的代码模块&#xff0c;对于函数的使用&#xff0c;一定是先定义&#xff0c;后使用。 使用函数的优势&#xff1a; ①我们可以…...

数据结构 -- 树形查找(二)平衡二叉树

平衡二叉树 定义 平衡二叉树&#xff08;AVL树&#xff09; – 树上的任意一点的左子树和右子树的高度之差不超过1 节点的平衡因子 左子树高-右子树高 平衡二叉树的结点的平衡因子的值只可能是-1、0、1 //平衡二叉树结点 typedef struct AVLNode{int key; //数据域int bal…...

day 29

类装饰器 类有修饰器&#xff0c;他的逻辑&#xff1a;接收一个类&#xff0c;返回一个修改后的类。例如 1. 添加新的方法或属性&#xff08;如示例中的 log 方法&#xff09;。 2. 修改原有方法&#xff08;如替换 __init__ 方法&#xff0c;添加日志&#xff09;。 3. 甚…...

Java 并发编程

黑马程序员深入学习Java并发编程 进程与线程 预备知识 java8&#xff0c;pom.xml <dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></depe…...

windows笔记本连接RKNN3588网络配置解析

这几天拿到了一块RKNN3588的板子,准备做视觉和Ros开发用,但是拿到后就蒙蔽了,不知道怎么ssh连到板子上去,更甚者不知道怎么配置网络让RKNN能够联网更新环境,这里记录一下整个过程。主要包括以下两个内容: 1.adb连接RKNN3588开发 2. 网口连接RKNN更新板子环境开发 adb连…...

C++ asio网络编程(8)处理粘包问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 粘包问题一、粘包原因总结&#xff1a; 二、如何处理粘包处理方法 三、完善消息节点MsgNode代码部分细节详解memcpy(_data, &max_len, HEAD_LENGTH);_data…...

【架构美学】Java 访问者模式:解构数据与操作的双重分发哲学

一、模式定义与核心思想 访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心目标是将数据操作与数据结构分离。通过定义一个独立的访问者类&#xff0c;使得新增对数据结构中元素的操作时&#xff0c;无需修改元素本身的类结构&#x…...

UE5无法编译问题解决

1. vs编译 2. 删除三个文件夹 参考...

Java可变参数与Collections工具类详解

Java可变参数与Collections工具类详解 一、可变参数&#xff08;Variable Arguments&#xff09; 1.1 基本概念 可变参数是Java 5引入的特性&#xff0c;允许在方法中定义数量可变的形参。其核心特点是&#xff1a;形参个数可以动态变化&#xff08;0个、1个、多个&#xff…...

Git版本管理命令reset

目录 命令 git reset 场景一只回退 工作区代码 场景二回退暂存库与工作区 场景三回退暂存库&#xff0c;工作区&#xff0c;版本库内容 命令 git reset git reset --[soft/mixed(默认)/hard] [文件] soft&#xff1a;只回退版本库中内容 mixed&#xff1a;回退暂存区&…...

改进模糊C均值时序聚类+编码器状态识别!IPOA-FCM-Transformer组合模型

改进模糊C均值时序聚类编码器状态识别&#xff01;IPOA-FCM-Transformer组合模型 目录 改进模糊C均值时序聚类编码器状态识别&#xff01;IPOA-FCM-Transformer组合模型效果分析基本描述程序设计参考资料 效果分析 基本描述 1.创新未发表&#xff01;研究亮点&#xff01;时序…...

Zookeeper入门(三)

Zookeeper Java 客户端 项目构建 ookeeper 官方的客户端没有和服务端代码分离&#xff0c;他们为同一个jar 文件&#xff0c;所以我们直接引入 zookeeper的maven即可&#xff0c; 这里版本请保持与服务端版本一致&#xff0c;不然会有很多兼容性的问题 1 <dependency>…...

使用Redission来实现布隆过滤器

简述布隆过滤器 布隆过滤器是一种概率型数据结构&#xff0c;它可以用来判断一个元素是否在一个集合中。我们当时使用的是Redisson实现的布隆过滤器。它的底层原理是&#xff0c;先初始化一个比较大的数组&#xff0c;里面存放的是二进制0或1。一开始都是0&#xff0c;当一个k…...

Seata源码—6.Seata AT模式的数据源代理一

大纲 1.Seata的Resource资源接口源码 2.Seata数据源连接池代理的实现源码 3.Client向Server发起注册RM的源码 4.Client向Server注册RM时的交互源码 5.数据源连接代理与SQL句柄代理的初始化源码 6.Seata基于SQL句柄代理执行SQL的源码 7.执行SQL语句前取消自动提交事务的源…...

Spring-Beans的生命周期的介绍

目录 1、Spring核心组件 2、Bean组件 2.1、Bean的定义 2.2、Bean的生命周期 1、实例化 2、属性填充 3、初始化 4、销毁 2.3、Bean的执行时间 2.4、Bean的作用域 3、常见问题解决方案 4、与Java对象区别 前言 关于bean的生命周期&#xff0c;如下所示&#xff1a; …...

目标检测新突破:用MSBlock打造更强YOLOv8

文章目录 YOLOv8的现状与挑战YOLO-MS的MSBlock简介MSBlock的工作原理MSBlock的优势 利用MSBlock改进YOLOv8替换YOLOv8主干网络中的部分模块代码实现&#xff1a;替换CSP模块为MSBlock 在YOLOv8的颈部&#xff08;Neck&#xff09;中插入MSBlock代码实现&#xff1a;在颈部区域插…...

[SpringBoot]Spring MVC(4.0)

获取Header 传统获取 header 从 HttpServletRequest 中获取 RequestMapping("/r8")public String r8(HttpServletRequest request) {String userAgent request.getHeader("User-Agent");return "userAgent: "userAgent;}使用浏览器访问后&…...

Linux概述:从内核到开源生态

Linux概述&#xff1a;从内核到开源生态 Linux 是当今计算机领域最核心的开源操作系统内核&#xff0c;其影响力已渗透到服务器、嵌入式设备、云计算甚至超级计算机等各个领域。本章将深入解析Linux的本质、核心架构及其背后的开源哲学。 1. Linux的本质&#xff1a;不只是“操…...

【ubuntu24.04】pycharm 死机结束进程

windows 远程pycharm到ubuntu执行程序 pycharm 在调试过程中&#xff0c;内存耗尽&#xff0c;然后死机了 pycharm 进程 (base) rootk8s-master-pfsrv:/home/zhangbin/下载# ps -ef | grep pycharm root 121245 3230568 0 5月14 pts/8 00:00:00 /bin/bash --rcfile …...

【PRB】深度解析GaN中最浅的受主缺陷

2025 年 1 月 16 日,Virginia Commonwealth University 的 M. A. Reshchikov 和 SUNY–Albany 的 B. McEwen 等人在《Physical Review B》期刊发表了题为《Identity of the shallowest acceptor in GaN》的文章,基于对 50 多个 Be 掺杂 GaN 样品的光致发光实验以及 Heyd-Scus…...

Flink CEP是什么?

Apache Flink 的 CEP&#xff08;Complex Event Processing&#xff0c;复杂事件处理&#xff09; 是 Flink 提供的一个库&#xff0c;用于在无界数据流中检测符合特定模式的事件组合。 &#x1f3af; 一、什么是 CEP&#xff1f; ✅ 定义&#xff1a; CEP 是一种从连续的数据…...

基于STM32的多传感器融合的设施农业小型搬运机器人避障控制系统设计

一、系统总体设计目标 针对设施农业场景中狭窄通道、障碍物多样(如农机具、作物植株、水管)的特点,设计一款基于 STM32 的小型搬运机器人避障控制系统。系统通过多传感器融合实现 360 环境感知,采用模糊 PID 控制算法实现平滑避障,满足温室、大棚等场景的搬运需求。 二、…...

从零开始实现大语言模型(十六):加载开源大语言模型参数

1. 前言 预训练大语言模型的难点不在于算法&#xff0c;而在于数据和算力&#xff0c;绝大多数企业和机构都没有预训练大语言模型的算力资源。在工业界的大语言模型应用实践中&#xff0c;通常会使用领域数据微调开源大语言模型参数&#xff0c;以构建领域大语言模型。 本文介…...

Spark,数据提取和保存

以下是使用 Spark 进行数据提取&#xff08;读取&#xff09;和保存&#xff08;写入&#xff09;的常见场景及代码示例&#xff08;基于 Scala/Java/Python&#xff0c;不含图片操作&#xff09;&#xff1a; 一、数据提取&#xff08;读取&#xff09; 1. 读取文件数据&a…...

java19

1.集合体系结构 注意&#xff1a; 2.collection遍历之迭代器遍历 一次循环只能一次next方法的原因&#xff1a; 原因&#xff1a;集合长度是单数就报错 3.collection遍历之增强for遍历 如何代码简写呢&#xff1a;集合名.for回车 4.collection遍历之Lambda表达式遍历 5.使用多态…...

从0到1吃透卷积神经网络(CNN):原理与实战全解析

一、开篇&#xff1a;CNN 在 AI 领域的地位 在当今人工智能&#xff08;AI&#xff09;飞速发展的时代&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;简称 CNN&#xff09;无疑是深度学习领域中最为耀眼的明星之一 。它就像是 AI 世界里的超级…...

建一个结合双向长短期记忆网络(BiLSTM)和条件随机场(CRF)的模型

构建一个结合双向长短期记忆网络&#xff08;BiLSTM&#xff09;和条件随机场&#xff08;CRF&#xff09;的模型&#xff0c;通常用于序列标注任务&#xff0c;如命名实体识别&#xff08;NER&#xff09;、词性标注&#xff08;POS Tagging&#xff09;等。下面我将通过口述的…...

mvc-ioc实现

IOC 1&#xff09;耦合/依赖 依赖&#xff0c;是谁离不开谁 就比如上诉的Controller层必须依赖于Service层&#xff0c;Service层依赖于Dao 在软件系统中&#xff0c;层与层之间存在依赖。我们称之为耦合 我们系统架构或者设计的一个原则是&#xff…...

符合Python风格的对象(再谈向量类)

再谈向量类 为了说明用于生成对象表示形式的众多方法&#xff0c;我们将使用一个 Vector2d 类&#xff0c;它与第 1 章中的类似。这一节和接下来的几节会不断实 现这个类。我们期望 Vector2d 实例具有的基本行为如示例 9-1 所示。 示例 9-1 Vector2d 实例有多种表示形式 &g…...

4.1.8文件共享

知识总览 基于索引节点的共享方式(硬链接)&#xff1a; 让不同用户的文件目录项指向同一个文件的索引节点 用户1创建文件1&#xff0c;并让文件目录项aaa指向了文件1&#xff0c;这个文件对应了一个索引节点&#xff0c;这个索引节点 包含了文件的物理地址和文件的其他属性信…...

[LevelDB]LevelDB版本管理的黑魔法-为什么能在不锁表的情况下管理数据?

文章摘要 LevelDB的日志管理系统是怎么通过双链表来进行数据管理为什么LevelDB能够在不锁表的情况下进行日志新增 适用人群: 对版本管理机制有开发诉求&#xff0c;并且希望参考LevelDB的版本开发机制。数据库相关从业者的专业人士。计算机狂热爱好者&#xff0c;对计算机的…...

普通用户的服务器连接与模型部署相关记录

普通用户的服务器连接与模型部署相关记录 一、从登录到使用自己的conda 1.账号登陆&#xff1a; ssh xxx172.31.226.236 2.下载与安装conda&#xff1a; 下载conda&#xff1a; wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh 安装con…...

WebSocket解决方案的一些细节阐述

今天我们来看看WebSocket解决方案的一些细节问题&#xff1a; 实际上&#xff0c;集成WebSocket的方法都有相关的工程挑战&#xff0c;这可能会影响项目成本和交付期限。在最简单的层面上&#xff0c;构建 WebSocket 解决方案似乎是添加接收实时更新功能的前进方向。但是&…...

架构思维:构建高并发扣减服务_分布式无主架构

文章目录 Pre无主架构的任务简单实现分布式无主架构 设计和实现扣减中的返还什么是扣减的返还返还实现原则原则一&#xff1a;扣减完成才能返还原则二&#xff1a;一次扣减可以多次返还原则三&#xff1a;返还的总数量要小于等于原始扣减的数量原则四&#xff1a;返还要保证幂等…...

C++函数基础:定义与调用函数,参数传递(值传递、引用传递)详解

1. 引言 函数是C编程中的核心概念之一&#xff0c;它允许我们将代码模块化&#xff0c;提高代码的可读性、复用性和可维护性。本文将深入探讨&#xff1a; 函数的定义与调用参数传递方式&#xff08;值传递 vs 引用传递&#xff09;应用场景与最佳实践 2. 函数的定义与调用 …...