多线程编程的演化

从来没有正儿八经的学过,这里一些不成熟的感觉,主要来自一篇关于 C++ 的博客。这篇文章里面的视频是精华,也让我重新了解了一些相关的东西,玩 boost 的人总是走在最前沿啊。

  • HPX 这个库有时间可以琢磨下,据说用 user thread,然后靠 million 个 thread 来解决问题的
  • C++ 标准的 future 的扩展 API,boost 已经实现了,有机会用它写个例子玩玩

最初对多线程的编程还是那些什么“创建线程”,然后怎么中断这些东西,后来玩 Java 的时候看见了 thread pool + future 的组合,不久 C++ 里面也出现了类似的 API,通过这层 API 一般程序员都可以避免直接与 lock、condition variable、等等诡异的结构打交道。实际上,显式使用锁最容易造成意想不到的问题,比如 MPI 类型的程序,很可能需要通过 barrier 之类的同步,而一旦其中一部分主机出现异常,其他节点就会干等,浪费资源。

因此从这个意义上来说,通过并行的加速源自:任务分割的颗粒度,产生的等候时间。颗粒度高就会产生较长的等候,颗粒度细就会出现程序难懂,因此并行编程的世界里,最优的结果都是非常有针对性的(多少 core、多少内存、多少…)。

一个常见的并行的 pattern 是 recursive 过程,通常每个任务被分解成子任务,然后父任务收集子任务的结果返回,这个经典的 pattern 的经典例子就是 Fibonacci 数列,我们可以认为 f(n) = f(n - 1) + f(n - 2) 是通过不同“线程”完成的结果合并获得的当前结果,当然这只是为了显示这种问题的基本结构,实际操作中不会真有人这样去算这个数列的。

线程的日子里面这种程序很难受的,有了 thread pool 和 future,一般我们会将 f(n - 1) 的计算扔到 thread pool 上获得一个 future,之后本线程计算 f(n - 2),然后将结果加上 future.get。这里的问题在于通过 get 获得结果是 blocking 的,意味着这里出现了等待,有没有可能我们只在最后一个 future 上 get,而中间的结果减少等待呢?这就是前面所谈到的 resumable function:我们通过 await 标注的结果 f(n - 1)f(n - 2) 并不发生计算,编译器为其生成一段代码,碰到 await 立即返回,一旦两者计算完毕返回这个函数时就会将两者剩下的操作继续执行(相加),看对应视频的时候想到了 coroutine,果然有人问了。这个策略其实可以用现有的 future 和扩展 API 来实现:这相当于两个操作:

  • 等候子任务完成,这可以看成是一个 future
  • future 结束后执行一段逻辑(通常可以利用 lambda 传递一个 callback)

也就是说这个 recursive 的过程应该返回一个 future,这个 future 含有处理子任务合并的逻辑。这个部分常用一个 data flow 的 pattern 来做。似乎 AWS 某个 simple workflow 的服务就是用 future 搭出来的一个 workflow。

为了演示这个抽象的概念,我们可以用 scala 来看看,但是至于 scala 是否能跟视频里面一样说的可以多个 stack 共用一个实际 stack 就不得而知了。

case class fibo (n: Int) {
  def value: Future[Int] =
    n match {
      case m < 2 => future {1}
      case m => {
        val a = fibo (n - 1).value
        val b = fibo (n - 2).value
        Future.reduce (List (a, b)) (_ + _)
      }
    }
}

不过从 scala 的 API 设定来看就没提供一个 get 之类的函数,逼着大家直接用 future 到底,最后通过类似 onSuccess 之类的 callback 来处理结果。

fibo (n).value.onSuccess { case v:Int =>
  println (v)
}

学习 Future API 的一个副产品是 partial function,这算是个 Function 的子类型,比如 List 之类也有接受 Function 和 PartialFunction 的两个 API:map 和 collect,partial function 可以只对“能处理”的输入处理。通常 partial function 使用类似上面的 case statement,你甚至可以将其赋值给一个 PartialFunction 对象,并且 partial function 可以通过 orElse 复合别的 partial function。以上 onSuccess 就算是接受一个 partial function,因为这只处理了成功的 case,而 future 运行可能会抛出异常。如果希望 handle 两个 case 可以用 onComplete,那个接受的是 Try,通过匹配 Success/Failure 可以分别处理之。

最后提一个叫 promise 的概念。其实 scala 通过一个简单的 future 隐藏了 promise 这个东西,所谓的 promise 可以看成是产生 future 的一个控制器,每个 promise 都对应唯一的 future,通过 promise 你可以为 future 设置成功的返回值或者抛出异常等,换言之 future 函数其实是产生了一个 promise/future 一体的东西,提供的 anonymous function 的结果就是这个 promise 设置 future 的依据。

===========

似乎 twitter 还有自己的一个 future 的 API,提供了 get,scala 自己的 future API 的确是一个很诡异的设计。

—————–
And Zilpah Leah’s maid bare Jacob a second son.

Advertisements
多线程编程的演化

一个有关“多线程编程的演化”的想法

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s