利用高階函數實現協程

論文連接javascript

協程的概念與實現

協程(coroutine)這一律念最先在1963年由Convay提出,雖然在上世紀80年代受到冷落,但在此以後,協程在Lua、Python、Ruby、Kotlin等諸多主流語言中都發揮了重要的做用。然而,包括Java、Swift等在內的不少語言並不能原生支持協程。本文做者提出了一種利用高階函數來實現協程的方法,這能夠應用於幾乎全部編程語言。java

協程和函數很是相像,一般來講,兩者都接受若干參數,而後產生必定的結果。兩者的區別在於,協程能夠暫時掛起(一般會使用一個yield語句),保留其運行環境,並將控制權轉交給另外一個協程。在此以後,這一協程會從新得到控制權,繼續運行直到再次掛起或終止。因爲協程運行在同一個線程內,不存在資源共享的問題,無須使用鎖,於是就避免了死鎖的發生。編程

協程有不少不一樣的實現方法。這些方法間一個重要的分野就在於協程的實現是(全)對稱仍是半對稱。所謂「(全)對稱」,是指協程的切換能夠發生在任意兩個協程之間,而「半對稱」則表示協程的切換隻能在調用棧中直接的父/子(parent/child)之間發生。對稱實現的協程有利於對整個流程進行更加全面的把控,而半對稱的協程實現一般更容易理解、使用和調試。閉包

這兩種實現方式在最終的效果上並無差別(參見de Moura & Ierusalimschy, 2009app

另外一個重要的區別在於協程是不是「棧滿」(stackfull)的。棧滿的協程能夠從調用棧的更深層被掛起,而非棧滿的則不行。若是要在一個非棧滿的協程實現環境下,編寫一個異步非阻塞的程序,就要求全部函數都能做爲生成器,而且全部的函數調用都要顯式地調用內層嵌套函數的結果。異步

一樣的,這兩種實現方式在最終的效果上也沒有差別。編程語言

本文方法是一個半對稱非棧滿的協程實現。函數

協程實現:生成器方法

原文圖1 JavaScript利用生成器實現Fibonacci序列的計算

上圖給出了一個在JavaScript中利用生成器(generator)來實現協程的例子。函數內部的while(true)並不會一直運行,而是每次在yield處暫時掛起,直到下一次的.next()被調用時才繼續進行。ui

協程實現:高階函數方法(本文方法)

在這一部分做者給出了一個更加簡單的例子:利用協程實現兩個元素的加法。spa

首先是生成器的實現:

function * f(n) {
  const x = yield n
  yield n + x
}

const add2 = f(2)
add2.next() //=>2
add2.next(3) //=>5
add2.next(5) //=>undefined (由於一共只有兩次yield)
複製代碼

接下來是高階函數版本的實現:

function f(n) {
  let inst = 1
  return function(x) {
    switch (inst) {
    case 1 : inst += 1; return n
    case 2 : inst += 1; return n + x
    default: break
    }
  }
}

const add2 = f(2)
add2() //=>2
add2(3) //=>5
add2(5) //=>undefined
複製代碼

利用高階函數來實現協程的出發點很簡單:大部分支持高階函數的編程語言都實現了閉包(closure),而實現協程的關鍵就在於保存環境(現場),那麼就能夠藉助於高階函數的函數閉包,來保存協程掛起時的所在環境。與生成器版本相比,利用高階函數實現的協程不須要使用額外的語法(function*/yield/next)。

一樣的,能夠用高階函數來實現上一節中Fibonacci序列的例子。

function fib() {
  let inst = 1; let a = null; let b = null
  return function() {
    while (true) {
      switch (inst) {
      case 1:
        inst = 2; a = 1; b = 2
      case 2:
        inst = 3; return a
      case 3:
        inst = 2; const c = a; a = b; b = c + a
      }
    }
  }
}

const f = fib()
f() //=>1
f() //=>2
f() //=>3
f() //=>5
f() //=>8
複製代碼

那麼若是編程語言不支持閉包怎麼辦呢?那就須要自行實現相似閉包的保存環境機制,做者在附錄中給出了一個C語言的例子,能夠參考一下。

結語

這篇文章的內容到這裏就介紹完了。用高階函數的方式實現協程是否是很清晰明瞭呢?感興趣的話,就親自嘗試一下吧!


附:Fibonacci序列生成器的C語言實現

// Emulation of first order functions.
typedef struct {
  void* env;
  void* (*fn)(void*);
} function_t;

void* apply(function_t closure) {
  return closure.fn(closure.env);
}

// Rewriting of the fibonacci sequence coroutine.
typedef struct {
  int inst;
  int a;
  int b;
} fib_env;

void* fib_fo(void* e) {
  fib_env* fe = (fib_env*)(e);
  while (1) {
    switch (fe->inst) {
    case 1:
      fe->inst = 2; fe->a = 1; fe->b = 2;
    case 2:
      fe->inst = 3; return &(fe->a);
    case 3:
      fe->inst = 2; int c = fe->a; fe->a = fe->b; fe->b = c + fe->a;
    }
  }
}

function_t fib() {
  fib_env* env = (fib_env*)(malloc(sizeof(fib_env)));
  env->inst = 1;
  function_t closure = { env, &fib_fo };
  return closure;
}

// Example of invocation.
int main() {
  function_t g = fib();
  for (int i = 0; i < 10; ++i) {
    printf("%i\n", *(int*)(apply(g)));
  }
  free(g.env);
  return 0;
}
複製代碼
相關文章
相關標籤/搜索