闭关之 C++ 函数式编程笔记(五):系统设计和程序测试
目录
- 第12章 并发系统的函数式设计
- 12.1 Actor 模型:组件思想
- 12.2 创建简单的消息源
- 12.3 将反应流建模为 monad
- 12.3.1 创建宿 (sink) 接收消息
- 12.3.2 转换反应流
- 12.3.3 创建给定值的流
- 12.3.4 连接流
- 12.4 过滤反应流
- 12.5 反应流的错误处理
- 12.6 响应客户端
- 12.7 创建状态可修改的 Actor
- 12.8 用 Actor 编写分布式系统
- 第十三章 测试与调试
- 13.1 程序编译正确吗?
- 13.2 单元测试与纯函数
- 13.3 自动产生测试
- 13.4 测试基于 monad 的并发系统
第12章 并发系统的函数式设计
- Code
- Actor_Web_Service
12.1 Actor 模型:组件思想
- Actor 是一个可以接收和发送消息的独立组件
- 对消息逐个处理
- 最小的 Actor 类应该具备接收和发送消息的能力
- C++ 的 Actor 框架
- 在 actor-framework.org 可以找到该模型的完整实现
- 代替品,SObjectizer库,性能更好,但是没有内置的跨进程分布式 Actor 的支持
- 简化类型的 Actor
- 它不需要关心谁向谁发送消息
- 将由外部控制器负责
- 设计如下所述
- Actor 只能收发一种类型的消息(收发消息类型不一定相同)
- 如果需要支持输入输出不同类型的消息
- 使用
std::variant
或std::any
- 使用
- 如果需要支持输入输出不同类型的消息
- 在外部的控制器中指定某个 Actor 向哪个 Actor 发送消息,以便以函数式的方式组合 Actor
- 而不是由 Actor 自行选择发送消息的对象
- 尾部的控制器可以指定 Actor 的监听源(发送消息的 Actor)
- 外部的控制器还可以决定哪些消息异步处理,哪些不需要异步处理
- Actor 只能收发一种类型的消息(收发消息类型不一定相同)
- 它不需要关心谁向谁发送消息
- 注意
- 现在大多数软件使用了一种事件循环,用于异步传递消息。但这里并不实现这样的系统,这里重点关注易于调整,适用于任何事件驱动的系统设计
- Actor 分三种
- 宿 sink
- 只接收消息的 Actor
- 源 sources
- 只发送消息的 Actor
- 一般 Actor
- 即可以接收消息,也可以发送消息
- 宿 sink
- Code_12_1_1
12.2 创建简单的消息源
- 现代 C++ Json 库
- https://gitee.com/learnlov/mirrors_nlohmann_json.git
- 继承
std::enable_shared_from_this
- 允许
std::shared_ptr
管理实例,安全创建自己的共享指针 shared_from_this()
- 创建该实例另一个共享指针
- 允许
- Code
- service.ixx
12.3 将反应流建模为 monad
- 异步流(Asynchronous Stream) 或 反应流 (Reactive Stream)
- 可以有任意数目的值,对于每个新到达的值,都可以调用延续函数
- 看起来像集合
- 它们包含相同类型的元素
- 只是并不是所有的元素都可立即获取
- 反应流是不是 monad
- monad 应符合的条件
- 它必须是一个通用类型
- 需要一个构造函数,创建包含给定值的反应流实例的函数
- 需要转换函数,返回反应流的函数,该函数把来自源流的值转换后发出
- 需要一个连接函数,从给定的流接收所有的消息,并把它们逐个发送
- 需要遵守 monad 法则
- monad 应符合的条件
- 可以执行以下操作使反应流成为 monad
- 创建一个流转换 Actor
- 创建一个按给定值创建流的 Actor
- 创建一个可以同时监听多个流的 Actor,并发送这些流
12.3.1 创建宿 (sink) 接收消息
- 宿
- 是一个只接收消息而不发送的 Actor
- 单一所有者设计
- 使用移动语义和右值引用,保证宿对象是 sender 的唯一所有者
- 相似的,其他的 Actor 变成了它们各自发送者的所有者
- 者意味着,当流水线销毁时,所有Actor也被消耗
- 缺点
- 系统中不能有多个组件监听一个 Actor
- 也不能在不同的数据流中共享 Actor
- 解决思路
- 允许共享 Actor 的所有关系,并允许每个 sender 持有多个监听器
- Code
- sink.ixx
- 分离 sink 和 sink_impl 的原因在于
- 支持反应流中类 range 语法
- 两个 sink 函数根据传递参数数目的不同返回不同的类型
- 如果 sink 不是一个合适的函数,这是很难实现的
- 为每个创建的转换定义
operator |
- 每个函数接收任何发送者对象最为第一参数和定义了转换的_helper类作为参数,可以增强 main 函数的可读性
- Code
- main.ixx
12.3.2 转换反应流
- 把反应流变成一个 monad
- 最重要的任务是创建 transform 流修改器
- 它应接收一个反应流和任意一个函数作为参数
- 并返回一个新流
- 使用给定的函数对原来的流进行转换,并发送转换后的消息
- 换言之,transform 转换器是一个即可接收消息又可发送消息的 Actor
- 流转换器的实现
- transform.ixx
- 与宿 Actor 不同, transform 不会立即连接到它的发送者
- 如果没有人发送消息,也就不需要处理
- 只有当 on_message 被调用时
- 也就是当需要监听消息时,才需要监听发送者发送的消息
- 使用示例
- main.ixx
12.3.3 创建给定值的流
- transform 函数使得反应流成了一个仿函数,为了使它是一个正常的 monad,需要能够从给定的值创建流,而且需要 join 函数
- 实现一个简单的功能
- 给定一个值,或一系列值,根据给定值创建一个发送它们的流
- 这个流不接收任何消息,只是发送消息
- 给定一个值,或一系列值,根据给定值创建一个发送它们的流
- Code
- values.ixx
- 这个类可以用作反应流的 monad 构造函数,
- 示例
- 通过向宿对象发送消息,检查它能否正常工作
- main.ixx
- std::initializer_list
- 对 STL 的 container 的初始化
12.3.4 连接流
- 把反应流作为 monad 的最后一件事就是定义 join 函数
- 对于 join 函数,接收的消息都是新的流,需要监听来自这些流消息,并把它们进行传递
- Code
- join.ixx
- 示例
- main.ixx
12.4 过滤反应流
- 为了过滤,创建一个类似于 transform 的流修改器
- 它可以接收消息并只发送符合谓词要求的消息
- 与 transform 和 join 不同的是, 过滤器监听和发送的是同一类型的消息
- 用于需要剔除无效数据或不感兴趣的数据
- Code
- filter
- 示例
- main.ixx
12.5 反应流的错误处理
- 使用
expected<T, E>
作为错误处理数据结构- 添加 mtry 函数
- Code
- mtry.ixx
- 示例
- main.ixx
- 注意
- 这节代码没有跑起来
12.6 响应客户端
- 到目前为止,Web 服务只能接收客户端的请求但不能响应
- 需要创建一个类模板,保存消息和 socket 指针
- 还需要创建一个 reply 成员函数
- 可以向客户端发送信息
template <typename MessageType> struct with_client {MessageType value;tcp::socket *socket;void reply(const std::string& message) const{asio::async_write(*socket,asio::buffer(message, message.length()),[](auto, auto) {});} };
- 它是一个含有而外信息的通用类型
- with_client 创建 join 函数
- 需要逐个修改所有的转换,直到它们全部理解引入的 with_client 类型
- 应用示例
auto transform = [](auto f) {return reactive::operators::transform(lift_with_client(f));};auto filter = [](auto f) {return reactive::operators::filter(apply_with_client(f));};asio::io_service event_loop;auto pipeline =service(event_loop)| transform(trim)// Ignoring comments and empty messages| filter([] (const std::string &message) {return message.length() > 0 && message[0] != '#';})// Trying to parse the input| transform([] (const std::string &message) {return mtry([&] {return json::parse(message);});})// Converting the result into the bookmark| transform([] (const auto& exp) {return mbind(exp, bookmark_from_json);})| sink([] (const auto &message) {const auto exp_bookmark = message.value;if (!exp_bookmark) {message.reply("ERROR: Request not understood\n");return;}if (exp_bookmark->text.find("C++") != std::string::npos) {message.reply("OK: " + to_string(exp_bookmark.get()) + "\n");} else {message.reply("ERROR: Not a C++-related link\n");}});
- 注意:
- 这节的代码也没有跑起来。不过不影响,已经理解其思想
12.7 创建状态可修改的 Actor
- 可变状态是实现 join 转换的灵魂
- 需要保持源的活跃
- 应用场景
- 为了保证 service 的响应速度,不能对所有消息指定相同的优先级
- 客户端试图发起 DoS 攻击
- 消息限制
- 如果客户端收到一条需要处理的消息,则在一定时间间隔拒绝后续发来的消息
- 需要创建一个 Actor 接收消息并记住发送消息的客户端,并限制时间间隔。在一段时间后,才再次接收客户端消息。
- 这就是 Actor 具有可变状态
- 在普通并发系统中,可变需要同步,但是在基于 Actor 的系统中却不是这样,Actor 是一个独立于其他 Actor的单线程组件,因此不需要任何同步
- 消息限制
12.8 用 Actor 编写分布式系统
- Actor 并不关心它们是否位于同一线程、同一进程中的不同线程,同一计算机的不同进程或不同的计算机中,只要它们可以相互发送消息即可
- 因此 Actor 很容易地扩展服务的模式,无须修改它的主逻辑
- 如果改为分布式,只需要改变系统的消息投递机制
第十三章 测试与调试
13.1 程序编译正确吗?
- 把错误从运行时转移到编译时
- 编译时检测到的错误越多,运行时的错误就会越少
- 示例
- 使用更强的类型而不是原始值进行编码
- 创建一个处理距离的类
template < typename Representation, typename Ratio = std::ratio<1>> class distance {public:explicit constexpr distance(Representation value): value(value){}distance operator+ (const distance &other) const{return distance(value + other.value);}Representation value; };
- 根据不同的测量单位创建不同的类型
template <typename Representation> using meters = distance<Representation>;template <typename Representation> using kilometers = distance<Representation, std::kilo>;template <typename Representation> using centimeters = distance<Representation, std::centi>;template <typename Representation> using miles = distance<Representation, std::ratio<1609>>;namespace distance_literals {constexpr kilometers<long double> operator ""_km(long double distance){return kilometers<long double>(distance);}constexpr miles<long double> operator ""_mi(long double distance){return miles<long double>(distance);}}
- 当试图混合匹配不同的单位时,会触发编译错误
- 这时提供一个转换函数
- 以这种方式把运行时错误提前到编译期
13.2 单元测试与纯函数
13.3 自动产生测试
13.4 测试基于 monad 的并发系统
- 这三节的测试方法都是常规技巧
闭关之 C++ 函数式编程笔记(五):系统设计和程序测试相关推荐
- 闭关之 C++ 函数式编程笔记(一):函数式编程与函数对象
目录 前言 第一章 函数式编程简介 1.1 命令式与声明式编程比较 1.2 纯函数(Pure functions) 1.2.1 避免可变状态 1.3 以函数方式思考问题 1.4 函数式编程的优点 1. ...
- 闭关之 C++ 函数式编程笔记(四):monad 和 模板元编程
目录 第十章 monad 注意 10.1 仿函数并不是以前的仿函数 10.1.1 处理可选值 10.2 monad: 更强大的仿函数 10.3 基本的例子 10.4 range 与 monad 的嵌套 ...
- 闭关之 C++ 函数式编程笔记(二):偏函数、组合、可变状态与惰性求值
目录 第四章 以旧函数创建新函数 4.1 偏函数应用 4.1.1 把二元函数转成一元函数的通用方法 4.1.2 使用 std::bind 绑定值到特定的函数参数 4.1.3 二元函数参数的反转 (这节 ...
- 函数式编程笔记 01
Cousera 上 Functional Programming Prinples in Scala 的笔记. 编程范式 范式描述了某些科学学科中独特的概念或者思考模式. 主要的编程范式: 命令式编程 ...
- scala函数式编程笔记: 纯函数式状态
scala函数式编程:纯函数式状态读书笔记 Overview: 带状态的方法的声明式实现可能带有副作用,难以保持引用透明. 以纯函数式的方式实现带状态的函数的关键在于让状态更新是显式的,不要以副作用方 ...
- java8函数式编程笔记-科里化
java函数式编程-科里化 什么是函数? 在数学上,函数的定义为"它接受零个或多个参数,生成一个或多个结果" 而在java8中,函数的定义为像数学函数一样没有副作用的函数 复制代码 ...
- UNIX网络编程笔记(7):回射程序的UDP版本
1.UDP简介 UDP是一个简单的传输层协议,应用进程往一个UDP套接字写入数据,随后被封装到一个UDP数据报,进而又被封装到一个IP数据报,然后发送到目的地.UDP不保证UDP数据报会最终到达目的地 ...
- CC2640R2F学习笔记(五.第一个程序:GPIO点灯)
文章目录 前言 一.生成配置文件 二.导入工程"empty" 三.代码改动 四.实现点灯 前言 点灯似乎是所有MCU开发编写的第一个程序,使用CUBEMX来配置STM32初始化程序 ...
- 《沟通的方法》笔记五:职业性格测试
虽然没有两个人拥有同样的面孔,各种地方文化也存在着种种差异,但这些都只是一种表象,就本质而言,人类拥有同样的心理结构--史蒂芬•平克 书中脱不花介绍了四种主要类型的人的沟通模式,以及分别的沟通方式. ...
最新文章
- python 用for循环生成字典_我需要创建一个for循环来生成嵌套字典
- python监听文件更改记录_如何用机器人监听老板微信?
- linux 根目录爆满 解决 /dev/mapper/centos-root 100%问题
- dim private public static_static方法 (静态方法)
- CentOS创建快捷按钮并设置文件图标
- Visual Studio 2005 Beta2 简体中文版发布
- Redhat7 samba配置(部分)
- linux 重名名、删除文件操作
- Java中expecial,RxJava 学习笔记 (一)
- oracle pl/sql 游标,Oracle PL/SQL 关于游标的介绍
- Unity 5.x 游戏开发指南 HD版 final.pdf
- 基于springboot的多人聊天系统
- IT十八掌徐培成第三天笔记
- 米兔机器人恐龙拼图手册_米兔积木机器人(多形态组合、自平衡系统、App联动)...
- IMO 开放平台单点登录
- java无法下载jnlp_无法加载jnlp文件
- 【LeetCode】Day51-打家劫舍 III变形
- outlook你的邮件服务器证书无效,安卓手机outlook无法登录、添加帐户
- 【Chips】跨时钟域的亚稳态处理、为什么要打两拍不是打一拍、为什么打两拍能有效?
- 为什么latex中的宋体和黑体与word中不一样 如何设置字体样式 renewcommand和newcommand的区别
热门文章
- 解决windows电脑蓝屏的方法
- MySql怎么附加数据库
- 国内技术管理人员批阅google的“春运交通图”项目
- html标签怎么制作足迹地图,用百度地图API做个人足迹地图教程
- 爬虫实战【6】Ajax内容解析-今日头条图集
- CentOS6开启BBR加速
- 无尽对决怎么修改服务器地区,无尽对决怎么更改账号 | 手游网游页游攻略大全...
- 五大列级庄_波尔多1855年评定的列级名庄1-5级各有哪些?哪个产区的列级庄最多?...
- 传奇怎么设置不显示服务器,如何将传奇服务器未知神殿地图修改为不限制进出...
- 12:计算2的N次方