Scalaz(59)- scalaz-stream: fs2-程序并行运算,fs2 running effects in parallel
scalaz-stream-fs2是一种函数式的数据流编程工具。fs2的类型款式是:Stream[F[_],O],F[_]代表一种运算模式,O代表Stream数据元素的类型。实际上F就是一种延迟运算机制:F中间包含的类型如F[A]的A是一个可能会产生副作用不纯代码(impure code)的运算结果类型,我们必须用F对A运算的延迟机制才能实现编程过程中的函数组合(compositionality),这是函数式编程的标准做法。如果为一个Stream装备了F[A],就代表这个Stream会在处理数据元素O的过程中对O施用运算A,如果这个运算A会与外界交互(interact with outside world)如:文件、数据库、网络等的读写操作,那么这个Stream有数据元素I/O功能的需求。我们可以通过fs2 Stream的状态机器特性(state machine)及F[A]与外界交互功能来编写完整的数据处理(data processing)程序。如果能够在数据库程序编程中善用fs2的多线程运算模式来实现对数据库存取的并行运算,将会大大提高数据处理的效率。我们将在本篇着重讨论fs2在实现I/O程序中的有关方式方法。
首先,我们需要以整体Stream为程序运算框架,把与外界交互的运算A串联起来,然后通过Stream的节点来代表程序状态。我们首先需要某种方式把F[A]与Stream[F,A]关联起来,也就是我们所说的把一个F[A]升格成Stream[F,A]。fs2提供了Stream.eval函数,我们看看它的类型款式:
def eval[F[_], A](fa: F[A]): Stream[F, A] = attemptEval(fa) flatMap { _ fold(fail, emit) }
很明显,提供一个F[A],eval返回Stream[F,A]。这个返回结果Stream[F,A]的元素A是通过运算F[A]获取的:在一个数据库程序应用场景里这个A可能是个数据库连接(connection),那么F[A]就是一个连接数据库的操作函数,返回的A是个连接connection。这次我们来模拟一个对数据库表进行新纪录存储的场景。一般来说我们会按以下几个固定步骤进行:
1、连接数据库,获取connection连接
2、产生新数据(在其它场景里可能是读取数据然后更新)。这可能是一个循环的操作
3、将数据写入数据库
这三个步骤可以用Stream的三种状态来表示:一个源头(source)、传转(pipe transducer)、终点(sink)。
我们先示范如何构建源头:这是一种占用资源的操作,会产生副作用,所以我们必须用延迟运算方式来编程:
//用Map模拟数据库表
import scala.collection.mutable.Map
type DataStore = Map[Long, String]
val dataStore: DataStore = Map() //> dataStore : fs2Eval.DataStore = Map()
case class Connection(id: String, store: DataStore)
def src(producer: String): Stream[Task,Connection] =
Stream.eval(Task.delay { Connection(producer,dataStore)})
//> src: (producer: String)fs2.Stream[fs2.Task,fs2Eval.Connection]
这个示范用了一个mutable map类型来模拟会产生副作用的数据库表。我们把具体产生数据的源头用Connection.id传下去便于在并行运算示范里进行跟踪。在这个环节里我们模拟了连接数据库dataStore操作。
产生数据是在内存里进行的,不会使用到connection,但我们依然需要把这个connection传递到下个环节:
case class Row(conn: Connection, key: Long, value: String)
val recId = new java.util.concurrent.atomic.AtomicLong()
//> recId : java.util.concurrent.atomic.AtomicLong = 1
def createData(conn: Connection): Row =
Row(conn, recId.incrementAndGet, s"Producer $conn.id: at ${System.currentTimeMillis}")
//> createData: (conn: fs2Eval.Connection)fs2Eval.Row
val trans: Pipe[Task,Connection,Row] = _.map {conn => createData(conn)}
//> trans : fs2.Pipe[fs2.Task,fs2Eval.Connection,fs2Eval.Row] = <function1>
trans是个Pipe。我们可以用through把它连接到src。
向数据库读写都会产生副作用。下一个环节我们模拟把trans传递过来的Row写入数据库。这里我们需要用延迟运算机制:
def log: Pipe[Task, Row, Row] = _.evalMap { r =>
Task.delay {println(s"saving row pid:${r.conn.id}, rid:${r.key}"); r}}
def saveRow(row: Row) = row.conn.store += (row.key -> row.value)
val snk: Sink[Task,Row] = _.evalMap { r =>
Task.delay { saveRow(r); () } }
增加了个跟踪函数log。从上面的代码可以看出:实际上Sink就是Pipe,只不过返回了()。
我们试试把这几个步骤连接起来运算一下:
val sprg = src("").through(trans).repeat.take().through(log).to(snk)
//> sprg : fs2.Stream[fs2.Task,Unit] = evalScope(Scope(Bind(Eval(Snapshot),<function1>))).flatMap(<function1>).flatMap(<function1>).flatMap(<function1>).flatMap(<function1>)
sprg.run.unsafeRun //> saving row pid:001, rid:2
//| saving row pid:001, rid:3
//| saving row pid:001, rid:4
println(dataStore) //> Map(2 -> Connection(001,Map()).id: at 1472605736214, 4 -> Connection(001,Map(2 -> Connection(001,Map()).id: at 1472605736214, 3 -> Connection(001,Map(2 -> Connection(001,Map()).id: at 1472605736214)).id: at 1472605736245)).id : at 1472605736248, 3 -> Connection(001,Map(2 -> Connection(001,Map()).id: at 1472605736214)).id: at 1472605736245)
我们看到mutable map dataStore内容有变化了。
如果我们把以上的例子用并行运算方式来实现的话,应该如何调整?为方便观察结果,我们先在几个环节增加一些时间延迟:
implicit val strategy = Strategy.fromFixedDaemonPool()
implicit val scheduler = Scheduler.fromFixedDaemonPool()
def src(producer: String): Stream[Task,Connection] =
Stream.eval(Task.delay { Connection(producer,dataStore)}
.schedule(.seconds)) val trans: Pipe[Task,Connection,Row] = _.evalMap {conn =>
Task.delay{createData(conn)}.schedule(.second)}
下面我们把一些类型调整成Stream[Task,Stream[Row]],然后把concurrent.join函数掺进去:
val srcs = concurrent.join()(Stream(src(""),src(""),src(""),src("")))
//> srcs : fs2.Stream[fs2.Task,fs2Eval.Connection] = attemptEval(Task).flatMap
<function1>).flatMap(<function1>)
val recs: Pipe[Task,Connection,Row] = src => {
concurrent.join()(src.map { conn =>
Stream.repeatEval(Task {createData(conn)}.schedule(.second)) })
} //> recs : fs2.Pipe[fs2.Task,fs2Eval.Connection,fs2Eval.Row] = <function1>
def saveRows(row: Row) = { row.conn.store += (row.key -> row.value); row}
//> saveRows: (row: fs2Eval.Row)fs2Eval.Row
val snks: Pipe[Task,Row,Row] = rs => {
concurrent.join()(rs.map { r =>
Stream.eval(Task {saveRows(r)}.schedule(.second)) })
} //> snks : fs2.Pipe[fs2.Task,fs2Eval.Row,fs2Eval.Row] = <function1>
我们试着把它们连接起来进行运算:
val par = srcs.through(recs).take().through(log("before")).through(chnn).through(log("after"))
//> par : fs2.Stream[fs2.Task,fs2Eval.Row] = attemptEval(Task).flatMap(<function1>).flatMap(<function1>).flatMap(<function1>)
par.run.unsafeRun //> before saving pid:001, rid:3
//| before saving pid:003, rid:2
//| before saving pid:002, rid:4
//| before saving pid:001, rid:5
//| after saving pid:001, rid:3
//| after saving pid:003, rid:2
//| before saving pid:003, rid:6
//| after saving pid:002, rid:4
//| before saving pid:002, rid:7
//| after saving pid:001, rid:5
//| before saving pid:001, rid:8
//| before saving pid:003, rid:9
//| after saving pid:003, rid:6
//| after saving pid:002, rid:7
//| before saving pid:002, rid:10
//| before saving pid:004, rid:11
//| after saving pid:001, rid:8
//| after saving pid:003, rid:9
//| after saving pid:002, rid:10
//| after saving pid:004, rid:11
从跟踪函数显示可以看出before,after是交叉发生的,这就代表已经实现了并行运算。
下面是本篇示范源代码:
import fs2._
import scala.concurrent.duration._
object fs2Eval { //用Map模拟数据库表
import scala.collection.mutable.Map
type DataStore = Map[Long, String]
val dataStore: DataStore = Map()
case class Connection(id: String, store: DataStore)
implicit val strategy = Strategy.fromFixedDaemonPool()
implicit val scheduler = Scheduler.fromFixedDaemonPool()
def src(producer: String): Stream[Task,Connection] =
Stream.eval(Task.delay { Connection(producer,dataStore)}
.schedule(.seconds))
case class Row(conn: Connection, key: Long, value: String)
val recId = new java.util.concurrent.atomic.AtomicLong()
def createData(conn: Connection): Row =
Row(conn, recId.incrementAndGet, s"$conn.id: at ${System.currentTimeMillis}")
val trans: Pipe[Task,Connection,Row] = _.evalMap {conn =>
Task.delay{createData(conn)}.schedule(.second)} def log(pfx: String): Pipe[Task, Row, Row] = _.evalMap { r =>
Task.delay {println(s"$pfx saving pid:${r.conn.id}, rid:${r.key}"); r}}
def saveRow(row: Row) = row.conn.store += (row.key -> row.value) val snk: Sink[Task,Row] = _.evalMap { r =>
Task.delay { saveRow(r); () } } val sprg = src("").through(trans).repeat.take().through(log("")).to(snk)
//sprg.run.unsafeRun
//println(dataStore) val srcs = concurrent.join()(Stream(src(""),src(""),src(""),src("")))
val recs: Pipe[Task,Connection,Row] = src => {
concurrent.join()(src.map { conn =>
Stream.repeatEval(Task {createData(conn)}.schedule(.second)) })
} def saveRows(row: Row) = { row.conn.store += (row.key -> row.value); row}
val chnn: Pipe[Task,Row,Row] = rs => {
concurrent.join()(rs.map { r =>
Stream.eval(Task {saveRows(r)}.schedule(.second)) })
} val par = srcs.through(recs).repeat.take().through(log("before")).through(chnn).through(log("after"))
par.run.unsafeRun
Scalaz(59)- scalaz-stream: fs2-程序并行运算,fs2 running effects in parallel的更多相关文章
- Scalaz(52)- scalaz-stream: 并行运算-parallel processing concurrently by merging
如果scalaz-stream真的是一个实用的数据流编程工具库的话,那它应该能处理同时从多个数据源获取数据以及把数据同时送到多个终点(Sink),最重要的是它应该可以实现高度灵活的多线程运算.但是:我 ...
- 改善C#程序的建议10:用Parallel简化Task
在命名空间System.Threading.Tasks下,有一个静态类Parallel简化了在同步状态下的Task的操作.Parallel主要提供了3个有用的方法:For.ForEach.Invoke ...
- Cocos2d-x 3.2编译生成Android程序出错Error running command, return code: 2的解决方法
用Cocos2d-x 3.2正式版创建项目,结果使用cocos compile -p android编译生成APK程序,结果悲剧了,出现以下错误. Android NDK: Invalid APP_S ...
- [No0000189]改善C#程序的建议10:用Parallel简化Task
在命名空间System.Threading.Tasks下,有一个静态类Parallel简化了在同步状态下的Task的操作.Parallel主要提供了3个有用的方法:For.ForEach.Invoke ...
- Scalaz(55)- scalaz-stream: fs2-基础介绍,fs2 stream transformation
fs2是scalaz-stream的最新版本,沿用了scalaz-stream被动式(pull model)数据流原理但采用了全新的实现方法.fs2比较scalaz-stream而言具备了:更精简的基 ...
- Scalaz(58)- scalaz-stream: fs2-并行运算示范,fs2 parallel processing
从表面上来看,Stream代表一连串无穷数据元素.一连串的意思是元素有固定的排列顺序,所以对元素的运算也必须按照顺序来:完成了前面的运算再跟着进行下一个元素的运算.这样来看,Stream应该不是很好的 ...
- Scalaz(57)- scalaz-stream: fs2-多线程编程,fs2 concurrency
fs2的多线程编程模式不但提供了无阻碍I/O(java nio)能力,更为并行运算提供了良好的编程工具.在进入并行运算讨论前我们先示范一下fs2 pipe2对象里的一些Stream合并功能.我们先设计 ...
- Scalaz(45)- concurrency :Task-函数式多线程编程核心配件
我们在上一节讨论了scalaz Future,我们说它是一个不完善的类型,最起码没有完整的异常处理机制,只能用在构建类库之类的内部环境.如果scalaz在Future类定义中增加异常处理工具的话,用户 ...
- Scalaz(23)- 泛函数据结构: Zipper-游标定位
外面沙尘滚滚一直向北去了,意识到年关到了,码农们都回乡过年去了,而我却留在这里玩弄“拉链”.不要想歪了,我说的不是裤裆拉链而是scalaz Zipper,一种泛函数据结构游标(cursor).在函数式 ...
随机推荐
- [SDK2.2]Windows Azure Storage (15) 使用WCF服务,将本地图片上传至Azure Storage (上) 服务器端代码
<Windows Azure Platform 系列文章目录> 这几天工作上的内容,把项目文件和源代码拿出来给大家分享下. 源代码下载:Part1 Part2 Part3 我们在写WEB服 ...
- Qt Disable QDebug And Warning Output
如何禁止qDebug的输出 在项目开发的过程中,为了开发方便,我们常常在Qt的Application Output中输出一些内容,慢慢的. 有些qDebug就会被我们遗忘再角落里. 虽然对整个程序影响 ...
- 【ssh信任关系】解决信任关系不生效问题
配置的时候遇见点问题,发现即便将id_rsa.pub拷贝到了另一台机器上,信任也没有建立起来. 原因是另外一台机器上目录权限不对,可以通过su root后观察/var/log/message里的日志信 ...
- Sql Server系列:数据库组成及系统数据库
1. 数据库组成 数据库的存储结构分为逻辑存储结构和物理存储结构. ◊ 逻辑存储结构:说明数据库是由哪些性质的信息所组成.SQL Server的数据库不仅仅只是数据的存储,所有与数据处理操作相关的信息 ...
- Sql Server系列:通用表表达式CTE
1 CTE语法WITH关键字 通用表表达式(Common Table Express, CTE),将派生表定义在查询的最前面.要使用CTE开始创建一个查询,可以使用WITH关键字. CTE语法: WI ...
- PHP新的垃圾回收机制:Zend GC详解
概述 在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果re ...
- cookie属性详解
在chrome控制台中的resources选项卡中可以看到cookie的信息. 一个域名下面可能存在着很多个cookie对象. name字段为一个cookie的名称. value字段为一个cookie ...
- 使用karma测试平时写的小demo(arguments为例)
有人说前端自动化测试非常困难,我觉得确实如此.在项目中,我个人也不放心写的测试,还是要手动测试.但是我们平时写demo学习时,完全可以使用自动化测试. 传统demo 1,新建一个html 2,写入js ...
- Java中的网络编程
Java中的网路编程主要是Java的Socket编程,属于JavaEE中的高级的部分,以下内容是对java网路编程的一个小结,代码都是经过编译调试的 C/S程序应用:客户/服务器模式,如QQ客户端 ...
- Spire.Pdf 的各种操作总结
Spire.Pdf 的各种操作总结 简介 试验新产品总是给我带来许多挑战,当然这也是一个引进创新技术的好方法.在这里我要跟大家分享的是使用Spire.Pdf的过程,它是来自E-iceblue公司的轻 ...