Go语言推崇的CSP编程模型和设计思想,并没有引起很多Go开发者包括Go标准库作者的重视。标准库的很多设计保留了很浓的OOP的味道。本篇Blog想比较下从设计的角度看,CSP和OOP到底有什么区别。

下面,我们来看一个例子,如果我们有一个项目,需要做一个TCP连接中继器(请原谅我的用词)。我们先按照OOP来设计下:

  1. 系统的结构:需要有一个客户端和一个服务器端。分两个进程分别跑在不同机器上。
  2. 系统对象关系拆分(这里有所简化,E-R图等省略):连接中继器类--系统的主类config类--描述配置的类、connection类--每个连接一个conn类的实例、pipe类--提供一个管道,把上游的连接和下游的连接打通,把数据从A--pipe--B、encrypt工具类,提供各种加解密工具。
  3. 理清楚系统中各种对象(类)的作用关系,设计接口的细节。这里的接口,其实就是对象之间相互发送的同步阻塞的消息。
  4. 设计错误处理,日志等。
  5. 从性能方面审视整个设计,优化。

=====

好,我们再按CSP的思路来设计下,是这么一个过程:

  1. 我们需要一个 main 协程来处理各种命令行参数的配置,收集处理配置文件; 如果是server,那么初始化server的主协程 -- tcpRemote;如果是client,则启动client的主协程 -- tcpLocal;
  2. 分别设计 tcpRemote和tcpLocal。tcpRemote 是server主协程,负责在指定的端口监听,如果有新的连接连入,则启动一个协程处理 -- serverConn ; tcpLocal是client的主协程,负责在客户机的指定端口监听,如果有连入连接,则启动一个协程处理 -- clientConn;
  3. 分别设计 serverConn和clientConn。serverConn 处理新连入连接的请求,把连接交给shadow函数处理得到一个装饰后的连接,析出目的Addr,发起连接到远端,再交给 relay 函数处理。clientConn 处理新连入的连接请求,析出目的地址, 发起一个新的到server的连接,把连接交给shadow函数处理得到一个装饰后的连接,转发请求给server,接下来的工作交给 relay 函数来处理。
  4. shadow函数,接受一个连接返回一个连接,标准的装饰器。由于需要实现多种加密算法,所以shadow函数有很多实现。程序启动时,在初始化阶段根据配置,注册好对应的shadow函数实现。
  5. relay函数,一个桥接器,接受两个连接,异步转发两个连接中 上行 和 下行 的数据。分别用两个协程来实现数据的 上行 和 下行 的转发逻辑。
  6. 设计错误处理,日志等。
  7. 从性能方面审视整个设计,优化。

=====

OOP部分写的比较简略,但是设计思路还是能看出来的,OOP的设计 核心的围绕点是系统中的对象的种类、职责以及相互的关系;OOP在低并发的时代诞生,对于系统中动力分配是不怎么重视的。在遇到具有共性的点的时候,OOP多是用接口的形式表达,多个不同类实现同一个接口。

CSP的设计 核心的围绕点,是系统中的动力源,也就是系统中动力的来源。动力源在Go语言里就是goroutine;由于goroutine往往是通过闭包函数创建出来,所以闭包函数捕获的upvalue等,也就成了父goroutine和子goroutine之间的一种隐藏的协议。更重要的,每个协程,本质上就是在调度发生时,自动把 上下文 保存起来的回调函数。这大大简化了状态的维护工作。在遇到具有共性的点的时候,CSP是多用装饰器和桥接器,把系统中的共性用函数的参数表达出来。

这两种设计中,接口和函数其实可以相互转换,一个接口只有一个方法的等价于一个函数;而几个函数构成了固定的组合,在Go里面等价于实现了某个接口。所以,这种对共性抽象的方法并没有太大的差别,甚至有人就推崇在Java中,一个接口就只有一个方法。

=====

OOP、FP、CSP、Actor等思想,其实都是在做取舍,究竟要隐藏那些细节暴露那些功能。如果什么都不考虑,那就是汇编了(近似的说法)。没有最优的设计思想只有合适的设计思想。

无论OOP/FP/CSP/Actor模型,都是可以相互转换、替换和实现。

FP/CSP/Actor中大量用闭包,其实就是把OOP的结构体交给编译器去自动生成而已,每个闭包函数捕获的upvalues在各种支持闭包的语言中,多是交给编译生成一个特殊命名的结构体,并在闭包传递时一并生成实例并传递引用。这样就使得一些地方用于消息传递的结构体可以省略,很多时候在chan中传递一个func()比传递一个消息更加的方便。Go的chan可以看作是把传统OOP语言以 方法调用形式 表达的同步阻塞消息传递,改成了显式的消息传递,更好的是,多路分发和逆多路分发机制也集成在语言中。

OOP中的方法调用本质上就是一种同步阻塞的消息传递,这点在ObjC中表现的非常清楚,ObjC中每个方法调用本质上就是发送了一条消息给某对象(sendmsg是一个变参数函数)。OOP隐形约定了,所有的进程内的语言运行时级别的消息传递都是同步阻塞的。而Go/Erlang等CSP/Actor模型的语言,打破了这一点,提供了语言级别的异步非阻塞的消息传递。

如果我们把软件设计分成 装配、动力驱动、可变性 三个方面考虑。

OOP的装配工作量比CSP要大的多。每个接口的每个方法都可以看成是一个螺丝,只有你紧固了每一个螺丝,OOP设计的软件才可以运行。而CSP设计的程序,每一个协程的创建,都是一个装配点,仰赖方便的闭包机制,装配所需螺丝是一次性自动紧固的。这就是CSP在设计上的优势之一吧。

在动力驱动方面,OOP由于假设了方法调用是同步阻塞的消息传递,其动力驱动也比较原始,大部分是依赖操作系统提供的线程和进程机制。但是CSP则提供了异步的非阻塞消息机制,以及自动上下文保存的可中断函数(也就是协程)。这些机制使得CSP的动力驱动简单高效。

在可变性方面,OOP的合约是由接口和结构体来约束的,而CSP的合约是由函数签名和闭包的upvalues来约束的。函数的参数和返回值可以都是空,只用upvalues来隐式表达约束。因此CSP在可变性方面也是更优秀的。

P.S.

需要强调的是OOP并没有什么特别的不好的,相反OOP具有巨大的优势,就是容易设计。

CSP虽然会要求从设计上改变即有思路,耗费较多的脑力,但其设计方案简单容易扩展,具有巨大的优势。

https://my.oschina.net/linker/blog/1507315

引用来自“qgymje”的评论
在Go里,可以将CSP的概念融入到OOP中,最为简单的做法是将函数调用的参数与返回值的类型都改为channel,这样可以做到function call在一定程度(channel没满)上为异步,至于闭包context不是重点,这个概念在Actor Model里表示为state; OOP思想可以体现在实现Actor Model的语言里,CSP是一种特殊的Actor,关注点不同

引用来自“LinkerLin”的评论
嗯,这么理解是可以的。
我说的是设计上,可以不用先考虑chan的使用,只关注goroutine,设计好goroutine后,goroutine之间的通讯可以不用chan,而是用闭包捕获的upvalues来沟通。这样就省去了通讯数据结构的设计。或者在chan中传输func,也一样省去了通讯用数据结构。

无论OOP,CSP, Actor, 它们的关注点都是message passing,oop目前大多表现为同步,actor为异步,csp半同步半异步

基于 CSP 的设计思想和 OOP 设计思想的异同的更多相关文章

  1. 基于UML的高校教务管理系统的设计与实现

    一.基本信息 标题:基于UML的高校教务管理系统的设计与实现 时间:2018 出版源:南昌航空大学 领域分类:教育信息化:教务管理系统:UML:SSH:Oracle 二.研究背景 问题定义:高校教务管 ...

  2. 基于ESP32的智能家居管理系统的设计与实现

    基于ESP32的智能家居管理系统的设计与实现 ESP32的智能家居管理系统访问链接: https://www.cnblogs.com/easyidea/p/13101165.html 一.需求分析 1 ...

  3. 基于 Angularjs&Node.js 云编辑器架构设计及开发实践

    基于 Angularjs&Node.js 云编辑器架构设计及开发实践 一.产品背景 二.总体架构 1. 前端架构 a.前端层次 b.核心基础模块设计 c.业务模块设计 2. Node.js端设 ...

  4. 基于特定领域国土GIS应用框架设计及应用

              基于特定领域国土GIS应用框架 设计及应用              何仕国 2012年8月16日   摘要: 本文首先讲述了什么是框架和特定领域框架,以及与国土GIS 这个特定领 ...

  5. 【科研论文】基于文件解析的飞行器模拟系统软件设计(应用W5300)

    摘要: 飞行器模拟系统是复杂飞行器研制和使用过程中的重要设备,它可以用来模拟真实飞行器的输入输出接口,产生与真实系统一致的模拟数据,从而有效避免因使用真实飞行器带来的高风险,极大提高地面测发控系统的研 ...

  6. Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理

    基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客  QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3 ...

  7. 基于SSH框架的考勤管理系统的设计与实现

    基于SSH框架的考勤管理系统的设计与实现

  8. 基于Android平台的会议室管理系统具体设计说明书

    会议室管理系统具体设计说明书 第一部分  引言 1.编写目的 本说明对会议室管理系统项目的各模块.页面.脚本分别进行了实现层面上的要求和说明. 软件开发小组的产品实现成员应该阅读和參考本说明进行代码的 ...

  9. 基于Redis的限流系统的设计

    本文讲述基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本.   1.概念 In computer netw ...

随机推荐

  1. Hadoop配置文件 分类: A1_HADOOP 2014-08-19 12:48 1157人阅读 评论(1) 收藏

    部分内容参考:http://www.linuxqq.net/archives/964.html  http://slaytanic.blog.51cto.com/2057708/1100974/ ha ...

  2. ssion机制详解

    ssion机制详解   ref:http://justsee.iteye.com/blog/1570652 虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚sess ...

  3. MySQL的表空间管理

    表空间: MySQL没有真正意义上的表空间管理. MySQL的Innodb包含两种表空间文件模式,默认的共享表空间和每个表分离的独立表空间. 一般来说,当数据量很小的时候建议使用共享表空间的管理方式. ...

  4. oracle改动登录认证方式

    通过配置sqlnet.ora文件.我们能够改动oracle登录认证方式. SQLNET.AUTHENTICATION_SERVICES=(NTS);基于操作系统的认证 SQLNET.AUTHENTIC ...

  5. java 线程排查问题流程

    1. 通过top命令查看当前系统CPU使用情况,定位CPU使用率超过100%的进程ID:2. 通过ps aux | grep PID命令进一步确定具体的线程信息:3. 通过ps -mp pid -o ...

  6. [Postgres] Update and Delete records in Postgres

    Delete example: DELETE FROM movies ; Update example: UPDATE movies ;

  7. 第六章:任务执行——Java并发编程实战

    任务:通常是一些抽象的且离散的工作单元.大多数并发应用程序都是围绕"任务执行"来构造的,把程序的工作分给多个任务,可以简化程序的组织结构便于维护 一.在线程中执行任务 任务的独立性 ...

  8. FMDB数据库框架

    nFMDB   nFMDB n什么是FMDB pFMDB是iOS平台的SQLite数据库框架 pFMDB以OC的方式封装了SQLite的C语言API p nFMDB的优点 p使用起来更加面向对象,省去 ...

  9. System.nanoTime()和System.currentTimeMillis()性能问题

    ​ 之前给模块做性能优化的时候,需要将性能调到毫秒级,使用了System.nanoTime()和System.currentTimeMillis()对代码分片计时分析耗时操作,后发现在串行情况下性能达 ...

  10. java命名规则(转)

    1. JAVA源文件的命名 JAVA源文件名必须和源文件中所定义的类的类名相同. 2. Package的命名 Package名的第一部分应是小写ASCII字符,并且是顶级域名之一,通常是com.edu ...