参考:

https://www.cnblogs.com/blizzard8204/p/17563217.html

https://www.bennyhuo.com/2022/03/09/cpp-coroutines-01-intro/

本文不完整, 更新中

1 基本概念

什么是协程?
  • C++ 20 的协程是一个特殊函数, 具有挂起和恢复的能力. (可以不一次性执行)
  • 协程可用于异步编程, 提供了一种更轻量级的并发机制
  • 使用了 co_awaitco_yieldco_return 的函数都是协程
  • 协程的返回类型必须满足条件: [[#协程的返回类型]]
c++20协程的缺陷
当前协程的实现非常复杂, 目前只提供了一些语法糖之类的东西, 需要根据很多规则编写代码, 最后由编译器依靠这些代码生成更复杂的代码. 就像 QT 框架那样
因此当前协程的范式是不完善的
协程是如何实现的

协程会在开始执行时的第一步就使用 operator new 来开辟一块内存来存放信息

包含如下内容

  • 协程传入的参数
  • 返回的 promise_type 类型对象 (也就是说, 返回对象一开始就创建了)
  • 协程的其他状态 (比如挂起点)

因此当需要暂停时, 那些状态被保存着, 等待恢复时使用.

协程执行完成 或 被销毁之后,协程的状态也被销毁释放

无栈协程和有栈协程

有栈:

  • 为每一个协程创建一个独立的内存栈进行上下文的保存和函数调用
  • boost 实现了有栈协程

    无栈:
  • 一开始就会在上保存所有的协程函数的“临时变量”以及调用参数等上下文信息
  • 从协程函数里切换出来的时候,因为大多数东西都是保存在上的,所以切换动作可以很短很快
  • 现代 c++20使用的是无栈协程

每次创建协程时,编译器都会自动完成堆分配。
协程的返回类型要求

[[#promise_type 类型]]

  • 协程的返回类型 Result 必须能够匹配 __coroutine_traits_impl<Result> 的模板参数要求.
  • __coroutine_traits_impl<Result> 要求其 返回类型 Result 必须含有 promise_type
简单来说,就是返回值类型 `Result` 含有 `Result::promise_type`
它可以是嵌套类, 或者使用 `using` 引入了 `promise_type` 类型.
`__coroutine_traits_impl` 是什么?
- 它是 `coroutine_traits` 的实现, 也是它的基类
- 它是一个空类型体, 只是用于检查 返回结果是否含有内部类型 `promise_type`
协程的执行流程

协程体的执行

  1. 使用 operator new 分配协程返回类型对象. (该运算可以重载)
  2. 将协程函数实参复制到协程状态对象中, 复制时可以按值传递或按引用传递
  3. 构造 return_type::promise_type 对象
    • 其中 return_type 是协程返回类型
    • 构造 promise_type 对象时, 若它有一个构造函数形参和协程的形参一致, 则会调用那个构造函数, 并传入所有协程的实参; 否则调用默认构造器
  4. 调用 promise_type::get_return_object(), 并将返回值保留在局部变量中, 当协程挂起时, 该值将返回给调用点,
  5. 调用 promise_type.initial_suspend()
    • 然后对返回值进行 co_await 运算.
    • 如果返回值满足挂起条件, 则协程一开始就被挂起
    • 否则进入下一步, 执行协程体
  6. 执行协程体, 直到遇到 co_awaitco_yieldco_return

流程图

Program with c++ 20

![[Pasted image 20241229210902.png]]

1.3 co_await

co_await 运算符和执行流程
  • 用于挂起协程并等待某个异步操作完成
  • 必须和 [[#awaitable 类型的概念|awaitable类型]]一起使用
co_await awaitable_obj;

如果使用默认的 co_await 运算, 那么将自动执行:

  1. 调用 awaitable_obj 的 [[#await_ready()]]

    • 如果返回 false 则表示协程未准备好, 协程将被挂起
    • 如果返回 true 则直接执行协程体
  2. 如果被挂起, 则执行 [[#await_suspend()]], 随后控制权交给调用协程处的程序.
  3. 如果在挂起后被恢复, 则 进入 [[#await_resume()]]
    • await_resume() 的返回值作为 co_await 表达式的结果
    • 这个返回值是协程体需要的
  4. 协程恢复完成, 继续执行 co_await 之后的协程体.
co_await 执行的例子

在调试模式下, 单步执行该程序, 可清楚地看到 co_await 的流程和作用

struct Myawaiter {
bool await_ready() { //5
std::cerr << "await_ready\n";
return false;
} void await_suspend(std::coroutine_handle<> h) { //6
std::cerr << "await_suspend\n";
} int await_resume() { //8
std::cerr << "await_resume\n";
return 44;
}
}; struct promise_type; struct RT {
public:
using promise_type = ::promise_type;
using handle_type = std::coroutine_handle<promise_type>; handle_type _handle; //句柄成员 handle_type get_handle() { return _handle; } ~RT() {
}
}; struct promise_type {
RT get_return_object() { //1
std::cerr << "get_return_object\n";
return RT{._handle = std::coroutine_handle<promise_type>::from_promise(*this)};
} std::suspend_always initial_suspend() { //2
std::cerr << "initial_suspend\n";
return {};
} std::suspend_never final_suspend() noexcept { //11
std::cerr << "final_suspend\n";
return {};
} void return_void() { //10
std::cerr << "return_void\n";
} void unhandled_exception() { std::terminate(); }
}; RT cofun() {
co_await Myawaiter{}; //4
co_return; //9
} int main() {
RT r = cofun();
RT::handle_type h = r.get_handle();
h.resume(); //3
h.resume(); //7
return 0;
} /* 打印结果
get_return_object
initial_suspend
await_ready
await_suspend
await_resume
return_void
final_suspend
*/

1.4 co_return

co_return 作用
  • co_return 可以将一个值返回给协程的调用者。 这个值可以是任何类型,包括基本类型、自定义类型、甚至是 void
  • co_return 语句会终止当前协程的执行,并调用 promise_type::final_suspend(), 并将控制权返回给调用者。
  • 最后协程将结束了.
co_return 流程
  1. 当协程执行到 co_return 语句时,它会先计算返回值 (如果有的话), 计算返回值过程如下:

    • 调用 promise::return_value(): co_return 会调用与协程关联的 promise_type 对象的 return_value() 成员函数,并将计算得到的返回值传递给它。

      • promise::return_value() 函数负责处理返回值,例如将其存储在 promise 对象的成员变量中,或者进行其他自定义操作。
      • 如果 promise 对象没有定义 return_value() 函数,或者 co_return 语句没有返回值(例如 co_return;),则不会调用 return_value() 函数。
  2. 调用 promise::final_suspend()
  3. 协程的控制权返回给调用者
    • 调用者可以通过 std::coroutine_handle::promise() 函数获取 promise 对象,并访问其中存储的返回值。
co_return 执行的例子

在调试模式下, 单步执行该程序, 可清楚地看到 co_await 的流程和作用

struct Myawaiter {
bool await_ready() {
std::cerr << "await_ready\n";
return false;
} void await_suspend(std::coroutine_handle<> h) { std::cerr << "await_suspend\n"; } int await_resume() {
std::cerr << "await_resume\n";
return 44;
}
}; struct promise_type; struct RT {
public: using promise_type = ::promise_type;
using handle_type = std::coroutine_handle<promise_type>; handle_type _handle; //句柄成员 handle_type get_handle() { return _handle; } RT(std::coroutine_handle<promise_type> h) : _handle(h) {
std::cerr << "RT 构造函数\n";
}
~RT() {
std::cerr << "RT 析构函数\n";
} int get_val();
}; struct promise_type {
int value{};
RT get_return_object() {
std::cerr << "get_return_object()\n";
return RT{std::coroutine_handle<promise_type>::from_promise(*this)};
} std::suspend_always initial_suspend() {
std::cerr << "initial_suspend()\n";
return {};
} std::suspend_always final_suspend() noexcept {
std::cerr << "final_suspend()\n";
return {};
} void return_value(int x) {
std::cerr << "return_value()\n"; } void unhandled_exception() { std::terminate(); } promise_type() { std::cerr << "promise_type 构造函数()\n"; } ~promise_type() { std::cerr << "promise_type 析构函数()\n";
}
}; int RT::get_val() { return _handle.promise().value; } RT cofun() {
std::cerr << "----------------------------------开始co_await...\n";
co_await Myawaiter{};
std::cerr << "----------------------------------结束co_await...\n"; std::cerr << "----------------------------------开始co_return...\n";
co_return 1;
std::cerr << "----------------------------------结束co_return...\n"; // 不会执行
} int main() {
RT r = cofun();
RT::handle_type h = r.get_handle();
h.resume();
h.resume(); std::cerr << "协程设置的值为: " << r.get_val() << "\n";
h.destroy();
return 0;
} /* 输出结果 promise_type 构造函数()
get_return_object()
RT 构造函数
initial_suspend()
----------------------------------开始co_await...
await_ready
await_suspend
await_resume
----------------------------------结束co_await...
----------------------------------开始co_return...
return_value()
final_suspend()
协程设置的值为: 0
promise_type 析构函数()
RT 析构函数 */

1.5 co_yield

co_yield 流程
  1. 当协程执行到 co_yield expression; 语句时,会发生以下事情:

    • expression 被计算,其结果将作为返回值, 返回给调用者
    • 控制权返回给协程的调用者。
    • 协程的执行状态被保存,包括局部变量, 寄存器值等。
  2. 返回值给调用者: 协程的调用者会接收到 co_yield 表达式的结果。
    • promise_type 中的成员变量来保存结果
    • 调用者可以通过一些接口来查询该结果.
  3. 恢复协程执行:当调用者希望继续执行协程时,可以调用 std::coroutine_handle<promise_type>::resume() 函数。
    • 此时,协程会从之前 co_yield 语句中断的地方恢复执行。
    • 协程可以使用 co_await 关键字等待异步操作完成,也可以使用 co_return 返回最终结果。
todo

也是一个 C++ 协程中的关键字

用于暂停协程的执行并返回值给调用者。

每次调用 co_yield 都会产生一个挂起点, 并调用函数 promise_type::yield_value()

co_yield 相关的函数实际上是在 promise 对象中定义的,用于处理 co_yield 的行为。 主要的函数有:

  1. yield_value():

    • 作用: 处理 co_yield 表达式的值。
    • 参数: co_yield 表达式的值会作为参数传递给 yield_value() 函数。
    • 返回值: yield_value() 函数的返回值没有特殊意义,因为协程在 co_yield 处已经暂停了。
    • 示例:
struct MyPromise {
// ...
std::suspend_always yield_value(int value) {
// 处理 co_yield 传递的值
currentValue = value;
std::cout << "Yielded value: " << value << std::endl;
return {}; // 返回 std::suspend_always 表示挂起协程
}
// ...
};
  1. await_transform():

    • 作用: await_transform() 本身不是专门为 co_yield 服务的,但它可以与 co_yield 配合使用,对 co_yield 返回的对象进行自定义处理。
    • 参数: co_yield 表达式的值会作为参数传递给 await_transform() 函数。
    • 返回值: await_transform() 函数的返回值决定了协程的行为:
      • 返回一个 awaitable 对象:协程会等待该 awaitable 对象完成后再继续执行。
      • 返回 std::suspend_always:协程会立即挂起。
      • 返回 std::suspend_never:协程会继续执行,不会挂起。
    • 示例:
struct MyPromise {
// ...
std::suspend_always await_transform(int value) {
// 处理 co_yield 传递的值
std::cout << "Custom processing: " << value << std::endl;
return {}; // 挂起协程
}
// ...
};

总结:

  • co_yield 不是函数,而是一个用于暂停协程并返回值的关键字。
  • yield_value() 函数用于处理 co_yield 表达式的值。
  • await_transform() 函数可以与 co_yield 配合使用,对 co_yield 返回的对象进行自定义处理。

例子

  • 待测试
#include <iostream>
#include <coroutine> // 定义 promise 类型,用于自定义协程的行为
struct GeneratorPromise {
int current_value; std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {} // 用于获取协程返回的对象
GeneratorPromise& get_return_object() { return *this; } // 用于处理 co_yield 表达式
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
}; // 定义协程类型
struct Generator {
using promise_type = GeneratorPromise; coroutine_handle<promise_type> handle; Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); } // 获取 co_yield 返回的值
int next_value() { return handle.promise().current_value; }
}; // 定义一个生成从 0 到 max 的整数序列的协程
Generator generate_numbers(int max) {
for (int i = 0; i <= max; ++i) {
co_yield i;
}
} int main() {
// 创建协程
auto generator = generate_numbers(5); while (generator.handle()) { //当协程句柄有效时, 则可以继续
std::cout << generator.next_value() << " ";
generator.handle().resume(); //继续协程
} std::cout << std::endl;
return 0;
}

附录: 一些在协程中的重要类型

总结

如何在外部获取协程句柄?

如何在外部获取和协程关联的 promis_type 对象?

如果在外部获得协程的返回值?

awaiter 类型和它的成员函数

awaiter 类型

等待器类型可被用于 co_await 运算.

必须含有三个可见的成员函数:

  • await_ready
  • await_suspend
  • await_resume

当对一个 awaiter 类型进行 co_await 运算时, 将依次发生如下:

  1. 调用 await_ready(), 根据返回值决定是否挂起

    • 返回 true, 不挂起, 则继续执行协程体
    • 返回 false, 挂起, 则执行 await_suspend(), 此时协程体本身被阻塞
  2. 当进入 await_suspend 后, 可以在适当的时候进入 await_resume, 继续执行协程体
await_ready() 成员
  • 它返回 bool 值, 用于检查并判断是否需要挂起协程.

    • 返回 true:表示协程可以直接继续执行 (已经准备好了),不需要挂起
    • 返回 false:表示协程需要挂起, 一旦挂起, 将进入 await_suspend
await_suspend() 成员
  • await_ready 返回 false 导致协程被挂起时, 将调用这个函数
  • 它接收一个 [[#std coroutine_handle 类型]] 的参数
    • 这个参数实际上是当前协程的句柄,它指向了这个协程的执行上下文.
    • 它的实参自动传递, 并不需要手动传递

await_suspend 的返回类型有多种

  • 返回 void 表示当前协程挂起之后, 将执行权还给当初调用点
  • 返回 true,表示当前协程挂起之后, 将恢复当前协程的函数
  • 返回 false,则恢复执行当前协程
    • 注意此时不同于 返回 true 的情形,此时协程已经挂起,await_suspend 返回 false 相当于挂起又立即恢复
  • 返回其他协程的 coroutine_handle 对象,这时候返回的 coroutine_handle 对应的协程被恢复执行
  • 抛出异常,此时当前协程恢复执行,并在当前协程当中抛出异常

例子

  • todo 例子不完整
class awaiter{
public:
await_ready(){return false;} //需要挂起 void await_suspend(coroutine_handle<> h){ //传入句柄
} constexpr bool await_suspend(coroutine_handle<> h){
std::cerr<<"await_suspend\n";
return false; //
}
} struct ReturnType; ReturnType mycorotine(){ }
await_resume() 成员
  • 当协程恢复执行时调用
  • 它的返回值作为 co_await 表达式的结果
suspend_never 和 suspend_alaways 类型

源码

struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
}; struct suspend_always{
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};

suspend_neverawait_ready 总是返回 true, 这将总是挂起协程.

这里的 coroutine_handle<> 实际上是类型 coroutine_handle<void>

例子

struct promise_type{
//... std::suspend_never initial_suspend(){
std::<<"不会挂起的\n";
return {};
}
};

awaitable 类型

awaitable 和 awaiter 的联系
  • 可以进行 co_await 运算的类型 都称为 awaitable 类型
  • awaitable 类型包含了 awaiter 类型
  • 也包含那些通过 重载 co_await 运算, 从而可进行 co_await 运算的类型
  • 或者可以一步 隐式转换为 可进行 co_await 运算的类型
`awaitable` 是一个概念, 是一种接口或约定
`awaiter` 这是 `awaitable` 的具体实现

例子

class Awaiter_String: public std::string{
using std::string::string; };
  • todo 待完善例子

    例子
class Awaitable_String : public std::string{
}

std::coroutine_handle 类型

coroutine_handle 类型介绍
  • 是一个类型模板, 定义在 corotine 头文件中
  • 模板实参一般是 [[#promise_type 类型]], 或者为空
  • 通过该类型的对象, 可以手动管理协程的生命周期
template <typename _Promise>
struct coroutine_handle{
//...
};
coroutine_handle 的常见成员函数

通过句柄的一些成员函数, 可以控制协程的状态.

源码

constexpr explicit operator bool() const noexcept{
return bool(_M_fr_ptr);
} bool done() const noexcept { return __builtin_coro_done(_M_fr_ptr); } void operator()() const { resume(); } void resume() const { __builtin_coro_resume(_M_fr_ptr); } void destroy() const { __builtin_coro_destroy(_M_fr_ptr); }

解释

  • resume():用于恢复协程的执行。如果协程已经完成或销毁,则行为未定义。
  • destroy():销毁协程,这会清理协程的状态并释放资源。应当在协程不再需要时调用。
  • done():返回一个布尔值,指示协程是否已经完成
  • operator() 实际上也是调用 resume 恢复协程
`__builtin_coro_destroy`, `__builtin_coro_resume` 等这些都是编译器内置的函数, 没有源码, 不具备可移植性
获取协程的句柄

由于协程在开始时, 就构造了返回对象, 以及 promise_type 对象.

因此这个 promise_type 对象就和协程对应. 可以从 promise_type 定位协程.

那么理应从 promise_type 对象可以获取 协程句柄.

根据上面思想, 协程库的 coroutine_handle 有如下静态函数 from_promise

只要传入一个 promise_type 对象, 该函数可以构造并返回协程的句柄.

//源码
template <typename _Promise>
struct coroutine_handle {
//... static coroutine_handle from_promise(_Promise &__p) {
coroutine_handle tmp;
tmp._M_fr_ptr =
__builtin_coro_promise(
(char *)&__p,
__alignof(_Promise),
true
);
return tmp;
}
}
从句柄获取协程的 promise_type 对象

使用 coroutine_handlepromise() 成员, 可以获取与协程句柄对应的 promie_type 对象.

//源码
template <typname _Promise>
struct coroutine_handle{
    _Promise& promise() const {
        return *static_cast<_Promise*>(
        __builtin_coro_promise(this->__handle_,
       alignof(_Promise),
       false
       )
      );
    }
};

promise_type 类型

  • 协程的返回类型为 RT ,则 RT 内必须含有类型 RT::promise_type
  • promise_type 是自定义类型.
  • 协程有时候不必直接返回一个对象 (不必写一个 return 语句)
    • 因为协程的返回对象在协程开始时 已经构建好了
    • 即使不做任何返回, 协程的状态区域也保存着返回值.
    • promise_type 可以定义一个 get_return_object() 成员, 它可以构造返回对象.
struct myPromise{ //在返回类型外部定义 promise_type
//...
}; struct Return_Type{
using promise_type = myPromise;
friend promise_type;
//...
};
get_return_object()
  • 这个成员函数是必须定义的,它负责创建协程的返回值 RT 类型对象
  • 它的返回类型必须是 协程返回类型 RT
  • 在协程一开始时, 它就会调用 get_return_object(), 构造这个对象. 并返回给协程的调用点

例子

struct RT{
struct promise_type{
RT get_return_object(){//...
}
//...
}
} RT cofunc(){ //协程函数
//
} int main(){
RT r = cofunc(); // 协程一开始就构建好了这个对象r, 这个对象一般包含着协程句柄信息.
}
initial_suspend()

当协程一开始时, 实际将发生如下调用

co_await return_type::promise_type.initial_suspend();
  • initial_suspend() 函数返回一个 [[#awaitable 类型]] 的对象,

    • 返回值一般是 std::suspend_neverstd::suspend_always

      • 返回 std::suspend_never 表示不挂起
      • 返回 std::suspend_always 或自定义的 awaiter 则表示挂起
  • 然后这个对象参与 [[#co_await 流程]] 运算
`suspend_never` 和 `suspend_always` 是什么?
- 它们都是 awaiter 类型, 参看下面源码
- `suspend_never`中 ,`await_ready`函数返回`true`, 表示准备好了,不会挂起
final_suspend()

这个成员函数在协程执行到最后(即遇到 co_return 或函数结束)时被调用

它的返回值决定协程是否在结束前挂起.

struct final_awaiter{
bool await_ready(){ return false;}
void await_suspend(coroutine_traits<> h){
h.destroy();
}
void await_resume(){}
}; struct promise_type{
//... final_awaiter final_suspend(){
return {};
}
};
unhandled_exception()

当协程内部抛出未捕获的异常时,会调用这个函数

return_void() 和 return_value()

协程的返回类型

在下文中, 我们使用 Return_type 或者 RT 指代返回类型

coroutine_traits 类型 和 协程返回类型要求

源码

// C:\msys64\mingw64\include\c++\14.2.0\coroutine
template <typename _Result, typename... _ArgTypes>
struct coroutine_traits; template <typename _Result, typename = void>
struct __coroutine_traits_impl {}; template <typename _Result>
// 用requires子句和表达式来约束_Result类型, 必须含有 promise_type
requires requires { typename _Result::promise_type; }
struct __coroutine_traits_impl<_Result, void>{
using promise_type = typename _Result::promise_type;
}; template <typename _Result, typename... _ArgTypes>
struct coroutine_traits : __coroutine_traits_impl<_Result> {};
  • coroutine_traits 从给定的协程返回类型 _Result 中提取出 promise_type
  • 它只用于检查协程体的 返回类型 是否合格
  • 编译器会根据协程的返回类型 _Result 自动推导出该类型的 promise_type,并使用它来进行协程的管理

21. C++快速入门--协程 Coroutine 入门的更多相关文章

  1. 一个有趣的小例子,带你入门协程模块-asyncio

    一个有趣的小例子,带你入门协程模块-asyncio 上篇文章写了关于yield from的用法,简单的了解异步模式,[https://www.cnblogs.com/c-x-a/p/10106031. ...

  2. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

  3. (zt)Lua的多任务机制——协程(coroutine)

    原帖:http://blog.csdn.net/soloist/article/details/329381 并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上 ...

  4. 协程coroutine

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...

  5. qemu核心机制分析-协程coroutine

    关于协程coroutine前面的文章已经介绍过了,本文总结对qemu中coroutine机制的分析,qemu 协程coroutine基于:setcontext函数族以及函数间跳转函数siglongjm ...

  6. Lua的多任务机制——协程(coroutine)

    并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上有这么两种多任务技术,一种是抢占式多任务(preemptive multitasking),它让操作系统来决定 ...

  7. Python并发编程协程(Coroutine)之Gevent

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...

  8. Unity协程Coroutine使用总结和一些坑

    原文摘自 Unity协程Coroutine使用总结和一些坑 MonoBehavior关于协程提供了下面几个接口: 可以使用函数或者函数名字符串来启动一个协程,同时可以用函数,函数名字符串,和Corou ...

  9. 【Unity】协程Coroutine及Yield常见用法

    最近学习协程Coroutine,参考了别人的文章和视频教程,感觉协程用法还是相当灵活巧妙的,在此简单总结,方便自己以后回顾.Yield关键字的语意可以理解为“暂停”. 首先是yield return的 ...

  10. Python之协程(coroutine)

    Python之协程(coroutine) 标签(空格分隔): Python进阶 coroutine和generator的区别 generator是数据的产生者.即它pull data 通过 itera ...

随机推荐

  1. Rsync 秒杀一切备份工具,你能手动屏蔽某些目录吗?

    引言 Rsync 是一种快速且通用的命令行实用程序,可通过远程shell在两个位置之间同步文件和文件夹. 使用 Rsync,可以镜像数据,创建增量备份,并在系统之间复制文件.复制数据时,你可能要根据文 ...

  2. Abp源码分析之Abp本地化

    aspnetcore mvc 实现本地化 新建mvc项目 修改Program.cs using Microsoft.AspNetCore.Localization; using Microsoft.A ...

  3. Webshell流量分析之哥斯拉Godzilla&冰蝎Behinder

    目录 哥斯拉 冰蝎 哥斯拉和冰蝎相较于菜刀蚁剑,它们的通信流量是加密的,有比较好的抗检测能力. 菜刀和蚁剑流量分析:Webshell流量分析之菜刀Chopper&蚁剑AntSword 哥斯拉 ...

  4. 初步学习Nuxt3

    Nuxt3 用于制作ssr 网页 支持vue3 所有的语法,并且支持了TypeScript, vite+vue3+composition api + ts.SPA单页面应用不能进行SEO优化,SSR应 ...

  5. golang之go-spew

    github: https://github.com/davecgh/go-spew 我们在使用Golang(Go语言)开发的过程中,会通过经常通过调试的方式查找问题的原因,解决问题,尤其是当遇到一个 ...

  6. golang之http请求库go-resty

    github: https://github.com/go-resty/resty go-resty 特性# go-resty 有很多特性: 发起 GET, POST, PUT, DELETE, HE ...

  7. highcharts中的仪表盘样式

    仪表盘的样式如下: 是双表盘展示 左边的图中minorTickInterval的值为null,右边的minorTickInterval的值为"auto" 左边的图中lineColo ...

  8. vue中方法中数据已更新,但是视图却没有变化解决方法

    今天在项目中碰到这样一个问题: 从父组件中传过来的props中的数据,在子组件中想加入一个变量.在created中加入变量,在方法中打印次变量是有的,但是当变量发生变化之后,视图中是响应不到的. 解决 ...

  9. Cygwin:windows下的Linux系统

    Cygwin是啥?Cygwin是一个可原生运行于Windows系统上的POSXI兼容环境. 是的,我又开新专辑了<零基础swoole学习笔记>.不是我太贪心,而是最近半年和小伙伴一直在用s ...

  10. Codeforces Round 890 (Div. 2)

    Tales of a Sort 题解 找到最大的能够产生逆序对的数即可 暴力\(O(n^2)\)枚举即可 const int N = 2e5 + 10, M = 4e5 + 10; int n; in ...