Future/Promise 执行逻辑

scala Future 有几个要点,第一个是 tryAwait 需要借助 CowndownLatch 实现,第二个是可以在 Promise 挂载回调函数

首先,大致看下 Scala concurrent 的架构

DefaultPromise -> AbstractPromise -> Promise(concurrent) -> Promise[Trait] -> Future[Trait] -> Awaitable

在 package 外使用的 Promise 是 Promise[Trait], 其实 DefaultPromise 也是有 map, flatMap 方法的,只不过不能用而已,DefaultPromise 是 scala promise 的唯一实现类

理解 link Promise

我没能完全理解 link promise 怎么实现垃圾回收的,

在 flatMap 中有一个 linkRootOf 函数,从 Promise 的注释中也可以看到 promise link 是一个很重要的概念,它解决了 flatMap 函数组合形成无限长的链后的 memory leak 问题

The ability to link DefaultPromises is needed to prevent memory leaks when using Future.flatMap. The previous implementation of Futhre.flatMap used onComplete handlers to propagate to the ultimate value of a flatMap operation to its promise. Recursive calls to flatMap built a chain of onComplete handlers and promises. Unfortunately none of the handlers or promises in the chain could be collected until the handers has been called detached, which only happended when the final flatMap future was completed. (In some situations, such as infinte streams, this would never actually happen.) Because of the fact that the promise implementation internally created references between promises, and these reference were invisible to user code, it was easy for user code to accidentally build large chains of promises and thereby leak memory.

结合 flatMap 函数理解

def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = {
import impl.Promise.DefaultPromise
val p = new DefaultPromise[S]()
onComplete {
case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
case Success(v) => try f(v) match {
// If possible, link DefaultPromises to avoid space leaks
case dp: DefaultPromise[_] => dp.asInstanceOf[DefaultPromise[S]].linkRootOf(p)
case fut => fut.onComplete(p.complete)(internalExecutor)
} catch { case NonFatal(t) => p failure t }
}
p.future
}

每次 flatMap 函数都会创建 DefaultPromise 变量,这个变量通过返回值传递到函数外,使它在上一层 scope 可见,如果无限创建不能被 GC 回收,那么内存很快就会被占满,而 stream 类型的数据流很可能就是无限长的,所以这个 DefaultPromise 变量一定要回收掉。

Example

// 添加 sleep 对分析控制流走向很有帮助
Future { Thead.sleep(3000), 1 }
.flatMap { x => { Thread.sleep(20000), 2} }
.flatMap { y => { Thread.sleep(50000), 3} }

Stage 1:

Future { Thread.sleep(3000); 1}

第一个 Future 调用 object Future.apply 方法,创建 PromiseCompletingRunnable, 放到线程池里运行,运行完毕后(几秒之后),会调用 promise complete Try 方法,此时还没调用。

Stage 2:

.flatMap { x => {Thread.sleep(20000), 2}}

complete 逻辑先不分析,然后是第一个 flatMap 方法,flatMap 方法在上面已经给出,不过我这里先把 flatMap 方法展开,去掉不重要或无关的代码

def flatMap(f: T => Future[S]): Future[S]
val p = DefaultPromise[S]
val callBackFunction = {
case Success(v) => f(v) match
case dp: DefaultPromise => dp.linkRootOf(p)
} val runnable = new CallbackRunnable(callbackFunction)
getState match
case r: Try => runnable(r)
case DefaultPromise => compressRoot().dispatcherOrCallback(runnable)
case listener: List[] => updateState(listenr, runnable::listener) p.future

flatMap 实际上只做了回调函数注册的功能,在上面的 promise complete 执行时,会调用这些 callbackFunction.

DefaultPromise 初始化时,State = Nil, 所以注册回调函数的时候,state 会被设置成 runnable.

Stage 3:

第一个 flatMap 函数执行,假设

f0 = Future{}
f1 = f0.flatMap {} // f0.state = runnable
f2 = f1.flatMap {} // f1.state = runnable

那么 stage 3 就是在 f1 上添加回调函数

Stage 3:

假设,第一个 Future 运算完毕,开始返回,promise complete result 开始执行了,complete 调用 tryComplete 函数

def tryComplete(r: Try)
getState match
case list: List[] => updateState(list, r); list.foreach(exec)
case DefaultPromise => ...

返回值为 Success(1), 执行刚才注册的回调函数 callBackFunction, f(v) 返回 Future 类型,实际上是 DefaultPromise 类型,这个操作也是通过线程池调用,异步执行,然后走到 dp.linkRootOf(p),注意,这个 dp 不再是 this 了,而是新产生的 Future, 而 p2 是 flatMap 里新创建的。

Stage 4:

    private def link(target: DefaultPromise[T]): Unit = if (this ne target) {
getState match {
case r: Try[_] =>
if (!target.tryComplete(r.asInstanceOf[Try[T]])) {
// Currently linking is done from Future.flatMap, which should ensure only
// one promise can be completed. Therefore this situation is unexpected.
throw new IllegalStateException("Cannot link completed promises together")
}
case _: DefaultPromise[_] =>
compressedRoot().link(target)
case listeners: List[_] => if (updateState(listeners, target)) {
if (!listeners.isEmpty) listeners.asInstanceOf[List[CallbackRunnable[T]]].foreach(target.dispatchOrAddCallback(_))
} else link(target)
}
}

因为 dp 是新创建的,且当前值还未返回(异步执行中),state = Nil, 所以这里会把状态更新为 target 也就是 p2, 没有需要执行的回调函数。

Stage 5:

当 f2 返回了,会执行 promise complete try, 进入 tryComplete 逻辑,上一次,tryComplete 走的是 List() 分支,而这次,因为 state 上 Stage 4 换成了 target, 也就是 P2, 所以这次改走 DefaultPromise 分支,调用 P2 上的 Listener 也就是第三个 flatMap 的逻辑。这样,chain 就跑起来了

Stage 6:

第二个 flatMap 依然执行创建 DefaultPromise, 注册回调函数的逻辑,

Promising Linking的更多相关文章

  1. NewRelicAgent(CustomAnalyticEvent.cxx.o), building for iOS simulator, but linking in object file built for OSX, for architecture x8(botched)

    昨天遇到一个问题,在项目swift1.2适配swift2.0的过程中,修改完毕之后,运行报错如下: /Pods/NewRelicAgent/NewRelic_iOS_Agent_5.1.0/NewRe ...

  2. Android Studio Linking an external C++ project 时候 报Invalid file name. Expected: CMakeLists.txt

    Android Studio 右键Linking an external C++ project 时候 报Invalid file name. Expected: CMakeLists.txt错误 查 ...

  3. Linking Containers Together

    Linking Containers Together In the Using Docker section we touched on connecting to a service runnin ...

  4. Chapter 7:Linking

    概述: 在linux上,从c源码到可执行文件主要需要经历translator(compiler.assembler)生成object file,再经由linker连接成executable objec ...

  5. error #10234-D: unresolved symbols remain error #10010: errors encountered during linking;

    error #10234-D: unresolved symbols remain error #10010: errors encountered during linking;: include ...

  6. How does the compilation and linking process work?

    The compilation of a C++ program involves three steps: Preprocessing: the preprocessor takes a C++ s ...

  7. 容器互联(linking)

    容器互联(linking)是一种让多个容器中的应用进行快速交互的方式. 它会在源和接受容器中间创建连接关系,接受容器可以通过容器名快速访问到源容器而不用指出具体的IP地址.

  8. Statically Linking freeglut

    It’s possible statically link freeglut into your applications, instead of dynamically linking agains ...

  9. English Phrases with THE – Linking the TH Sound

    English Phrases with THE – Linking the TH Sound Share Tweet Share Tagged With: The Word THE Study En ...

随机推荐

  1. 检查密码复杂度的C#正则表达式

    在用户注册与修改.重置密码时,强制密码达到一定的复杂度,是减少盗号的有效措施之一. 而在代码中检查密码复杂度就需要用到正则表达式,比如要求密码必须包含数字.小写或大写字母.特殊字符.字符数在8-30之 ...

  2. MVP

    引自: http://www.cnblogs.com/Leo_wl/archive/2013/05/03/3056299.html http://www.codeproject.com/Article ...

  3. 进程状态转换、CPU调度算法

    进程的状态转换 进程在运行中不断地改变其运行状态.通常,一个运行进程必须具有以下三种基本状态. 进程状态 执行态run:进程正在使用CPU 等待态wait:进程正在等待I/O完成,不在使用也不能使用C ...

  4. [ACM_模拟] ZJUT 1155 爱乐大街的门牌号 (规律 长为n的含k个逆序数的最小字典序)

    Description ycc 喜欢古典音乐是一个 ZJUTACM 集训队中大家都知道的事情.为了更方便地聆听音乐,最近 ycc 特意把他的家搬到了爱乐大街(德语Philharmoniker-Stra ...

  5. 最新QQ强制聊天代码,同时可判断好友关系

    QQ强聊虽然早就变成了一个传说,但现在依然可以实现. 小菜其实早就知道这个漏洞,但是一直没公布,前两天突然来兴致试了试,没想到漏洞依然存在. 然后小菜跑到了乌云漏洞报告平台举报漏洞,但没想到被腾讯鲁莽 ...

  6. p4 是否能自动merge

      总结: 1)如果在copy merge(-at)/auto merge(-am)后修改source branch,则可以自动被copy merge: 2)如果在manual merge后修改sou ...

  7. 易出错的C语言题目之一:宏定义与预处理

    1.写出下列代码的运行结果: #include<stdio.h> #include<string.h> #define STRCPY(a,b) strcpy(a##_p,#b) ...

  8. Atitit.软件兼容性原理与实践 v3 q326.docx

    Atitit.软件兼容性原理与实践 v3 q326.docx 1. 架构兼容性1 2. Api兼容性1 2.1. 新api  vs  修改旧的api1 3. Web方面的兼容性(js,html)1 3 ...

  9. paip.编程语言到底有没有优劣之分优秀之分

    paip.编程语言到底有没有优劣之分优秀之分 人有没有优秀之分之分呢??狗有没有优秀之分呢?? 当然是有的,有好人坏人的说法,或者精英平民的区分..狗也有好狗狗,坏狗,疯狗嘛.. 所以,自然,编程语言 ...

  10. React Ajax

    React 组件的数据可以通过 componentDidMount 方法中的 Ajax 来获取, 当从服务端获取数据库可以将数据存储在 state 中,再用 this.setState 方法重新渲染 ...