論文連接javascript
協程(coroutine)這一律念最先在1963年由Convay提出,雖然在上世紀80年代受到冷落,但在此以後,協程在Lua、Python、Ruby、Kotlin等諸多主流語言中都發揮了重要的做用。然而,包括Java、Swift等在內的不少語言並不能原生支持協程。本文做者提出了一種利用高階函數來實現協程的方法,這能夠應用於幾乎全部編程語言。java
協程和函數很是相像,一般來講,兩者都接受若干參數,而後產生必定的結果。兩者的區別在於,協程能夠暫時掛起(一般會使用一個yield
語句),保留其運行環境,並將控制權轉交給另外一個協程。在此以後,這一協程會從新得到控制權,繼續運行直到再次掛起或終止。因爲協程運行在同一個線程內,不存在資源共享的問題,無須使用鎖,於是就避免了死鎖的發生。編程
協程有不少不一樣的實現方法。這些方法間一個重要的分野就在於協程的實現是(全)對稱仍是半對稱。所謂「(全)對稱」,是指協程的切換能夠發生在任意兩個協程之間,而「半對稱」則表示協程的切換隻能在調用棧中直接的父/子(parent/child)之間發生。對稱實現的協程有利於對整個流程進行更加全面的把控,而半對稱的協程實現一般更容易理解、使用和調試。閉包
這兩種實現方式在最終的效果上並無差別(參見de Moura & Ierusalimschy, 2009)app
另外一個重要的區別在於協程是不是「棧滿」(stackfull)的。棧滿的協程能夠從調用棧的更深層被掛起,而非棧滿的則不行。若是要在一個非棧滿的協程實現環境下,編寫一個異步非阻塞的程序,就要求全部函數都能做爲生成器,而且全部的函數調用都要顯式地調用內層嵌套函數的結果。異步
一樣的,這兩種實現方式在最終的效果上也沒有差別。編程語言
本文方法是一個半對稱非棧滿的協程實現。函數
上圖給出了一個在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語言的例子,能夠參考一下。
這篇文章的內容到這裏就介紹完了。用高階函數的方式實現協程是否是很清晰明瞭呢?感興趣的話,就親自嘗試一下吧!
// 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;
}
複製代碼