C++20协程

news2024/11/18 19:47:10

简介

​ C++20协程只是提供协程机制,而不是提供协程库。C++20的协程是无栈协程,无栈协程是一个可以挂起/恢复的特殊函数,是函数调用的泛化,且只能被线程调用,本身并不抢占内核调度。

​ C++20 提供了三个新关键字(co_await、co_yield 和 co_return),如果一个函数中存在这三个关键字之一,那么它就是一个协程。

​ 协程相关的三个关键字:co_await、co_yield与co_return

  • co_yield some_value: 保存当前协程的执行状态并挂起,返回some_value给调用者
  • co_await some_awaitable: 如果some_awaitable没有ready,就保存当前协程的执行状态并挂起
  • co_return some_value: 彻底结束当前协程,返回some_value给协程调用者

协程相关的对象

协程帧(coroutine frame)

当 caller 调用一个协程的时候会先创建一个协程帧,协程帧会构建 promise 对象,再通过 promise 对象产生 return object。

协程帧中主要有这些内容:

  • 协程参数

  • 局部变量

  • promise 对象

这些内容在协程恢复运行的时候需要用到,caller 通过协程帧的句柄 std::coroutine_handle 来访问协程帧。

promise_type

promise_type 是 promise 对象的类型。promise_type 用于定义一类协程的行为,包括协程创建方式、协程初始化完成和结束时的行为、发生异常时的行为、如何生成 awaiter 的行为以及 co_return 的行为等等。promise 对象可以用于记录/存储一个协程实例的状态。每个协程桢与每个 promise 对象以及每个协程实例是一一对应的。

promise_type的接口

promise_type接口功能
initial_suspend()控制协程初始化完成后是否挂起
final_suspend()控制协程执行完后是否挂起
get_return_object()返回给 caller 一个对象
unhandled_exception()处理异常
return_void()调用co_return;时或者协程执行完后被调用
return_value(T)保存协程返回值。调用co_return xxx;的时候被调用,保存协程返回值
yield_value()调用co_yield xxx;的时候,会调用,保存协程返回值
await_transform()用于定制协程body中co_await xxx;语句的行为。定义该方法后,编译器会将出现在协程主体中的每个co_await xxx;转换为co_await promise.await_transform(xxx)

promise_type里的接口需要我们实现,promise_type里的接口是给编译器调用的

coroutine return object

它是promise.get_return_object()方法创建的,一种常见的实现手法会将 coroutine_handle 存储到 coroutine object 内,使得该 return object 获得访问协程的能力。

std::coroutine_handle

协程帧的句柄,主要用于访问底层的协程帧、恢复协程和释放协程帧。
程序员可通过调用 std::coroutine_handle::resume() 唤醒协程。

std::coroutine_handle接口

coroutine_handle接口作用
from_promise()从promise对象创建一个coroutine_handle
done()检查协程是否运行完毕
operator bool检查当前句柄是否是一个coroutie
operator()恢复协程的执行
resume恢复协程的执行(同上)
destroy销毁协程
promise获取协程的promise对象
address返回coroutine_handle的指针
from_address从指针导入一个coroutine_handle

coroutine_handle的接口不需要我们实现,可以直接调用

co_await、awaiter、awaitable

  • co_await:一元操作符;

  • awaitable:支持 co_await 操作符的类型;

  • awaiter:定义了 await_ready、await_suspend 和 await_resume 方法的类型。

co_await expr(expr是表达式) 通常用于表示等待一个任务(可能是 lazy 的,也可能不是)完成。co_await expr 时,expr 的类型需要是一个 awaitable,而该 co_await表达式的具体语义取决于根据该 awaitable 生成的 awaiter。

协程对象如何协作

以一个简单的代码展示这些协程对象如何协作:

Return_t foo () { 
    auto res = co_await awaiter; 
    co_return res ; 
}

Return_t:promise return object。

awaiter: 等待一个task完成。

图中浅蓝色部分的方法就是 Return_t 关联的 promise 对象的函数,浅红色部分就是 co_await 等待的 awaiter。

这个流程的驱动是由编译器根据协程函数生成的代码驱动的,分成三部分:

  • 协程创建;
  • co_await awaiter 等待 task 完成;
  • 获取协程返回值和释放协程帧。

协程的创建

Return_t foo () { 
    auto res = co_await awaiter; 
    co_return res ; 
}

foo()协程会生成下面这样的模板代码(伪代码),协程的创建都会产生类似的代码:

{
	co_await promise.initial_suspend();
  	try
  	{
    	coroutine body;
  	}
  	catch (...)
  	{
    	promise.unhandled_exception();
  	}
FinalSuspend:
  	co_await promise.final_suspend();
}

首先需要创建协程,创建协程之后是否挂起则由调用者设置 initial_suspend 的返回类型来确定。

创建协程的流程大概如下:

  • 创建一个协程帧(coroutine frame)
  • 在协程帧里构建 promise 对象
  • 把协程的参数拷贝到协程帧里
  • 调用 promise.get_return_object() 返回给 caller 一个对象,即代码中的 Return_t 对象

在这个模板框架里有一些可定制点:如 initial_suspend、final_suspend、unhandled_exception 和 return_value。

我们可以通过 promise 的 initial_suspend 和 final_suspend 返回类型来控制协程是否挂起,在 unhandled_exception 里处理异常,在 return_value 里保存协程返回值。

可以根据需要定制 initial_suspend 和 final_suspend 的返回对象来决定是否需要挂起协程。如果挂起协程,代码的控制权就会返回到caller,否则继续执行协程函数体(function body)。

PS:如果禁用异常,那么生成的代码里就不会有 try-catch。此时协程的运行效率几乎等同非协程版的普通函数。

co_await 机制

co_await 操作符是 C++20 新增的一个关键字,co_await expr 一般表示等待一个惰性求值的任务,这个任务可能在某个线程执行,也可能在 OS 内核执行,什么时候执行结束不知道,为了性能,我们又不希望阻塞等待这个任务完成,所以就借助 co_await 把协程挂起并返回到 caller,caller 可以继续做事情,当任务完成之后协程恢复并拿到 co_await 返回的结果。

所以 co_await 一般有这几个作用:

  • 挂起协程;
  • 返回到 caller;
  • 等待某个任务(可能是 lazy 的,也可能是非 lazy 的)完成之后返回任务的结果。

编译器会根据 co_await expr 生成这样的代码:

{
    auto&& value = <expr>;
    auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
    auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));
    if (!awaiter.await_ready()) //是否需要挂起协程
  	{
    	using handle_t = std::experimental::coroutine_handle<P>;
 
    	using await_suspend_result_t = decltype(awaiter.await_suspend(handle_t::from_promise(p)));
 
    	<suspend-coroutine> //挂起协程
 
    	if constexpr (std::is_void_v<await_suspend_result_t>)
    	{
      		awaiter.await_suspend(handle_t::from_promise(p)); //异步(也可能同步)执行task
      		<return-to-caller-or-resumer> //返回给caller
    	}
    	else
    	{
      		static_assert(
         		std::is_same_v<await_suspend_result_t, bool>,
         		"await_suspend() must return 'void' or 'bool'.");
 
      		if (awaiter.await_suspend(handle_t::from_promise(p)))
      		{
        		<return-to-caller-or-resumer>
      		}
    	}
 
    	<resume-point> //task执行完成,恢复协程,这里是协程恢复执行的地方
  	}
 
	return awaiter.await_resume(); //返回task结果
}

这个代码执行流程就是“协程运行流程图”中粉红色部分,从这个生成的代码可以看到,通过定制 awaiter.await_ready() 的返回值就可以控制是否挂起协程还是继续执行,返回 false 就会挂起协程,并执行 awaiter.await_suspend,通过 awaiter.await_suspend 的返回值来决定是返回 caller 还是继续执行。

正是 co_await 的这种机制是变“异步回调”为“同步”的关键。

C++20 协程中最重要的两个对象就是 promise 对象(恢复协程和获取某个任务的执行结果)和 awaiter(挂起协程,等待task执行完成),其它的都是“工具人”,要实现想要的的协程,关键是要设计如何让这两个对象协作好。

例子

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

namespace Coroutine {
    struct task {
        struct promise_type {
            promise_type() {
                std::cout << "1.task-promise_type():create promise object\n";
            }

            task get_return_object() {
                std::cout << "2.task-get_return_object():create coroutine return object, and the coroutine is created now\n";
                return {std::coroutine_handle<task::promise_type>::from_promise(*this)};
            }

            // initial_suspend()决定协程初始化后,是继续直接继续执行协程,还是挂起协程返回caller
            // 返回std::suspend_never,表示不挂起协程,会继续执行协程函数体(coroutine body)
            // 返回std::suspend_always,表示挂起协程,不会去执行coroutine body,程序的执行返回到caller那里
            std::suspend_never initial_suspend() {
                std::cout << "3.task-initial_suspend():do you want to susupend the current coroutine?\n";
                std::cout << "4.task-initial_suspend():don't suspend because return std::suspend_never, so continue to execute coroutine body\n";
                return {};
            }

            // 调用完void return_void()或者void return_value(T v)后,就会调用final_suspend()
            // 如果final_suspend返回std::suspend_never表示不挂起协程,那么协程就会自动销毁,先后销毁promise, 协程帧上得参数和协程帧;
            // 如果返回std::suspend_always则不会自动销毁协程,需要用户手动去删除协程。
            std::suspend_never final_suspend() noexcept {
                std::cout << "15.task-final_suspend():coroutine body finished, do you want to susupend the current coroutine?\n";
                std::cout << "16.task-final_suspend():don't suspend because return std::suspend_never, and the continue will be automatically destroyed, bye\n";
                return {};
            }

            // 如果协程是void没有返回值,那么就需要定义void return_void()
            // 如果有返回值那么就定义void return_value(T v),用来保存协程的返回值
            // return_value或者return_void,这两个方法只允许存在一个
            void return_void() {
                std::cout << "14.task-return_void():coroutine don't return value, so return_void is called\n";
            }

            void unhandled_exception() {}
        };

        std::coroutine_handle<task::promise_type> handle_;
    };

    struct awaiter {
        // 调用co_wait awaiter{};时调用await_ready()
        // 表示是否准备好,要不要挂起协程
        // await_ready()返回false一般表示要挂起协程,并执行await_suspend
        // 返回true说明协程已经执行完了,这时候调用await_resume返回协程的结果。
        bool await_ready() {
            std::cout << "6.await_ready():do you want to suspend current coroutine?\n";
            std::cout << "7.await_ready():yes, suspend becase awaiter.await_ready() return false\n";
            return false;
        }

        //await_suspend 的返回值来决定是返回 caller 还是继续执行。
        //返回void:协程执行权交还给当前协程的caller。当前协程在未来某个时机被resume之后,然后执行协程函数中co_await下面的语句
        //返回true:同返回void。
        //返回false:直接执行await_resume
        void await_suspend(std::coroutine_handle<task::promise_type> handle) {
            std::cout << "8.await_suspend(std::coroutine_handle<task::promise_type> handle):execute awaiter.await_suspend()\n";
            std::thread([handle]() mutable {
                std::cout << "11.lambada():resume coroutine to execute coroutine body\n";
                handle();//等价于handle.resume();
                std::cout << "17.lambada():over\n";
            }).detach();
            std::cout << "9.await_suspend(std::coroutine_handle<task::promise_type> handle):a new thread lauched, and will return back to caller\n";
        }

        //调用完await_resume后直接执行协程函数中co_await下面的语句
        void await_resume() {
            std::cout << "12.await_resume()\n";
        }
    };

    task test() {
        std::cout << "5.test():begin to execute coroutine body, the thread id=" << std::this_thread::get_id() << ",and call co_await awaiter{};\n"; //#1
        co_await awaiter{};
        std::cout << "13.test():coroutine resumed, continue execute coroutine body now, the thread id=" << std::this_thread::get_id() << "\n"; //#3
    }

    template<typename T>
    struct lazy {
    public:
        struct promise_type;

        lazy(std::coroutine_handle<promise_type> handle) : m_handle(handle) {
            std::cout << "3.lazy(std::coroutine_handle<promise_type> handle):Construct a lazy object" << std::endl;
        }

        ~lazy() {
            std::cout << "15.~lazy():Destruct a lazy object " << std::endl;
            m_handle.destroy();
        }

        T get() {
            std::cout << "6.lazy.get():I want to execute the coroutine now. call m_handle.resume()" << std::endl;
            if (!m_handle.done()) {
                m_handle.resume();
            }
            std::cout << "13.lazy.get():We got the return value...:" << m_handle.promise().value << std::endl;
            return m_handle.promise().value;
        }

        struct promise_type {
            T value = {};

            promise_type() {
                std::cout << "1.lazy-promise_type():Promise created" << std::endl;
            }

            ~promise_type() {
                std::cout << "16.lazy- ~promise_type():Promise died" << std::endl;
            }

            auto get_return_object() {
                std::cout << "2.lazy-get_return_object():create coroutine return object, and the coroutine is created now" << std::endl;
                return lazy<T>{std::coroutine_handle<promise_type>::from_promise(*this)};
            }

            auto initial_suspend() {
                std::cout << "4.lazy-initial_suspend():Started the coroutine" << std::endl;
                return std::suspend_always{};
            }

            auto final_suspend() noexcept {
                std::cout << "12.lazy-final_suspend():Finished the coroutine" << std::endl;
                return std::suspend_always{};
            }

            void return_value(T v) {
                std::cout << "11.lazy-return_value(T v):Got coroutine result " << v << std::endl;
                value = v;
            }

            void unhandled_exception() {
                std::exit(1);
            }

            //协程体中调用co_yield xxx;的时候调用yield_value(T val)
            auto yield_value(T val) {
                std::cout << "9.lazy-yield_value(T val): " << val << std::endl;
                value = val;

                //后续不再挂起协程,继续执行
                return std::suspend_never();

//                //后续继续挂起协程
//                return std::suspend_always();
            }

        };

        std::coroutine_handle<promise_type> m_handle;
    };

    lazy<int> my_coroutine() {
        std::cout << "7.my_coroutine():Execute the coroutine function body" << std::endl;
        std::cout << "8.my_coroutine():call---co_yield 66;" << std::endl;
        co_yield 66;
        std::cout << "10.my_coroutine():call---co_return 88;" << std::endl;
        co_return 88;
    }
} // namespace Coroutine

int main() {
    Coroutine::test();
    std::cout << "10.main():come back to caller becuase of co_await awaiter\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));

    std::cout << "-----------------------------------" << std::endl;

    auto coro = Coroutine::my_coroutine();
    std::cout << "5.main():call coro.get()" << std::endl;
    auto result = coro.get();
    std::cout << "14.main():The coroutine result: " << result << std::endl;

//    std::cout << "main():Second call coro.get()  " << std::endl;
//    result = coro.get();
//    std::cout << "main():The coroutine result2: " << result << std::endl;

    return 0;
}

输出结果:

1.task-promise_type():create promise object
2.task-get_return_object():create coroutine return object, and the coroutine is created now
3.task-initial_suspend():do you want to susupend the current coroutine?
4.task-initial_suspend():don't suspend because return std::suspend_never, so continue to execute coroutine body
5.test():begin to execute coroutine body, the thread id=1,and call co_await awaiter{};
6.await_ready():do you want to suspend current coroutine?
7.await_ready():yes, suspend becase awaiter.await_ready() return false
8.await_suspend(std::coroutine_handle<task::promise_type> handle):execute awaiter.await_suspend()
9.await_suspend(std::coroutine_handle<task::promise_type> handle):a new thread lauched, and will return back to caller
10.main():come back to caller becuase of co_await awaiter
11.lambada():resume coroutine to execute coroutine body
12.await_resume()
13.test():coroutine resumed, continue execute coroutine body now, the thread id=2
14.task-return_void():coroutine don't return value, so return_void is called
15.task-final_suspend():coroutine body finished, do you want to susupend the current coroutine?
16.task-final_suspend():don't suspend because return std::suspend_never, and the continue will be automatically destroyed, bye
17.lambada():over
-----------------------------------
1.lazy-promise_type():Promise created
2.lazy-get_return_object():create coroutine return object, and the coroutine is created now
3.lazy(std::coroutine_handle<promise_type> handle):Construct a lazy object
4.lazy-initial_suspend():Started the coroutine
5.main():call coro.get()
6.lazy.get():I want to execute the coroutine now. call m_handle.resume()
7.my_coroutine():Execute the coroutine function body
8.my_coroutine():call---co_yield 66;
9.lazy-yield_value(T val): 66
10.my_coroutine():call---co_return 88;
11.lazy-return_value(T v):Got coroutine result 88
12.lazy-final_suspend():Finished the coroutine
13.lazy.get():We got the return value...:88
14.main():The coroutine result: 88
15.~lazy():Destruct a lazy object 
16.lazy- ~promise_type():Promise died

Process finished with exit code 0

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/502877.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【DRF配置管理】如何建立swagger风格api接口文档

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 DRF应用和管理 【DRF配置管理】Django安装和使用DRF框架 【DRF配置管理】如何在视图函数配置参数(一) 【DRF配置管理】如何在视图函数配置参数(二) 【…

C. Enlarge GCD(内存的限制 + 数组的访问速度)

Problem - C - Codeforces Mr. F 有 n 个正整数 a1,a2,…,an。 他认为这些整数的最大公约数太小了。所以他想通过删除其中一些整数来扩大它。 但是这个问题对他来说太简单了&#xff0c;所以他不想自己做。如果你帮他解决这个问题&#xff0c;他会给你一些奖励分数。 你的任…

AntDB数据库携手金蝶Apusic应用服务器, 共促信创产业繁荣发展

日前&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称&#xff1a;亚信安慧&#xff09;与深圳市金蝶天燕云计算股份有限公司&#xff08;简称&#xff1a;金蝶天燕&#xff09;完成AntDB数据库与金蝶Apusic服务器软件V9.0、V10产品的兼容互认&#xff0c;兼容性良好&…

不是吧,3 : 00 面试,还没10分钟就出来了,问的也太...

从外包出来&#xff0c;没想到死在另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到2月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推我去…

Android WebRtc+SRS/ZLM视频通话(3):安装ZLMediaKit

Android WebRtcSRS/ZLM视频通话&#xff08;3&#xff09;&#xff1a;安装ZLMediaKit 来自奔三人员的焦虑日志 接着上一章内容&#xff0c;继续来记录ZLMediaKit的安装&#xff0c;这里的ZLMediaKit实际上和SRS的功能差不多&#xff0c;都是国内流媒体服务框架使用人数比价多&…

【SpringBoot】MyBatis与MyBatis-Plus分页查询问题

笔者写这篇博客是因为近期遇到的关于两者之间的分页代码差距&#xff0c;其实之前也遇见过但是没有去整理这篇博客&#xff0c;但由于还是被困扰了小一会儿时间&#xff0c;所以还是需要加深记忆。其实会看前后端传参解决这个问题很快、不麻烦。关于这两个框架的分页代码问题主…

物联网|整体介绍|蓝牙4.0BLE信道分析与拓扑分析|物联网之蓝牙4.0 BLE基础-学习笔记(1)

文章目录 课程整体介绍1、蓝牙4.0自身的优点2、开设这门课的重要性3课程的总体规划4.课程目的5.培训对象 蓝牙4.0BLE信道分析与拓扑分析蓝牙4.OBLE信道分析柘扑分析星型拓扑结构:扮演角色广播结构;星型结构的建立过程: 课程整体介绍 为什么我们要开设这么课程呢? 1、蓝牙4.0…

JDK17新特性之--JDK9到JDK17 String 新增的新方法

JDK9之后对String底层存储数据结构进行了重大的修改1&#xff0c;同步也增加了许多新的方法&#xff0c;主要有Text Blocks、chars()、codePoints()、describeConstable()、formatted()、indent()、isBlank()、isEmpty()、lines()、repeat()、strip()、stripLeading()、stripIn…

判断大小端的错误做法

这里不详细讲解大小端的区别&#xff0c;只讲解判断大小端的方法。 1.大端&#xff0c;小端的区别 0x123456 在内存中的存储方式 大端是高字节存放到内存的低地址 小端是高字节存放到内存的高地址 2.大小端的判断 1.错误的做法 int main() {int a0x1234;char c(char)a;if(…

CSS-Flex布局

01-标准流 标准流也叫文档流&#xff0c;指的是标签在页面中默认的排布规则&#xff0c;例如&#xff1a;块元素独占一行&#xff0c;行内元素可以一行显示多个。 02-浮动 基本使用 作用&#xff1a;让块元素水平排列。 属性名&#xff1a;float 属性值 left&#xff1a;…

全志H3-nanopi-duo2开发板GPIO驱动开发

1:获取对应开发板duo2的内核源码 从官网获取 [friendlyarm的nanopi-duo2](https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo2/zh#.E5.AE.9A.E5.88.B6.E5.91.BD.E4.BB.A4.E8.A1.8C.E7.9A.84.E6.AC.A2.E8.BF.8E.E4.BF.A1.E6.81.AF.EF.BC.88.E6.96.87.E5.AD.97LOGO.EF.B…

使用开源项目管理系统 Redmine 的优缺点

redmine是什么软件&#xff1f;Redmine是一款基于Ruby on Rails框架开发的开源项目管理软件&#xff0c;具有丰富的功能和高度可定制性。主要功能包括项目管理、问题跟踪、文档管理、时间跟踪以及多种报表。要安装使用Redmine&#xff0c;首先需要搭建Ruby on Rails运行环境&am…

网页和原生程序的交互方案

1 ActiveX和BHO是微软开发且闭源的&#xff0c;仅适用于IE 这里就不讨论了&#xff0c;这种方式会给用户带来很大的安全风险。而且也不符合html5标准&#xff0c;现在已经被市场抛弃。 2 搜索挂接&#xff08;URL SEARCHHOOK) 在window系统中&#xff0c;通过在注册表中&…

3.1 Linux启动Shell

系列文章目录 第1章 Linux Shell简介 第2章 Shell基础 第3章 Bash Shell基础命令 <本章所在位置> 第4章 Bash Shell命令进阶 第5章 Linux Shell深度理解 第6章 Linux环境变量 第7章 Linux文件权限 第8章 Linux文件系统的管理 第9章 Linux软件安装 第10章 Linux文本编辑器…

框架不是框框—应用框架的基本思想

软件构件化是21世纪软件工业发展的大势趋。工业化的软件复用已经从通用类库进化到了面向领域的应用框架。Gartner Group认为&#xff1a;“至少70%的新应用将主要建立在如软件构件和应用框架这类‘构造块’之上&#xff1b;应用开发的未来就在于提供一开放体系结构&#xff0c;…

http状态码301、302及304

http状态码分类&#xff1a; 1**&#xff1a;服务器收到请求&#xff0c;需要请求者继续执行操作 2**&#xff1a;成功&#xff0c;操作被成功接收并处理 3**&#xff1a;重定向&#xff0c;需要进一步的操作以完成请求 4**&#xff1a;客户端错误&#xff0c;请求包含语法错误…

Meta内容总监:Quest最初并非侧重游戏,VR用户画像每年都在变

2019年&#xff0c;随着Oculus Quest的发布&#xff0c;Quest应用商店应运而生。仅仅4年时间&#xff0c;就成为了发展速度最快的VR平台&#xff0c;吸引越来越多的开发者进入到Quest中去&#xff0c;并关注到VR生态。截至去年10月&#xff0c;Quest商店交易规模达15亿美元&…

云计算运维工程师好学吗?

云计算运维工程师作为2023年的热门IT职业之一&#xff0c;不仅在专业本身的技术内容和职业前景&#xff0c;还是整个互联网行业&#xff0c;乃至全行业对于云计算运维人才的需求等方面都有突出的表现&#xff0c;备受追捧的新IT职业。 所以从职业前景还是就业需求&#xff0c;…

2023年第一季度企业邮箱安全性观察

近日&#xff0c;Coremail邮件安全联合中睿天下发布《2023年第一季度企业邮箱安全研究报告》&#xff0c;对2023年第一季度的企业邮箱的安全风险进行了分析。 01、垃圾邮件环比增长21.19% 2023年Q1全国企业邮箱用户共收到各类垃圾邮件7.13亿封&#xff0c;相比2022年Q4季度环比…

SeaweedFS学习笔记:Filer服务,目录与文件

文章目录 1. 介绍2. 用法2.1 生成配置文件2.2 启动 filer 3. 读写流程3.1 读取流程3.2 写入流程 4. Filer Store4.1 复杂度4.2 Filer的使用场景 5. 数据加密5.1 对Volume server的数据进行加密 1. 介绍 文件系统&#xff0c;一般都离不开目录和文件&#xff0c;当我们把Seawee…