[譯] 快速介紹JavaScript中的CSP
Communicating Sequential Processes 的 7 個示例
CSP 是什么? 一般來說, 它是寫并行代碼的一套方案.
在 Go 語言里自帶該功能, Clojure 通過基于 Macro 的 core.async 來實現,
現在 JavaScript 通過 Generator 也能做支持了, 或者說 ES6 的功能.
為什么我要關心 CSP? 因為它強大啊, 而且高效, 而且簡單. 都這樣了你還想要什么? :)
好吧, 說細節. 怎樣使用呢?我們用 js-csp, 而且需要 generator 支持, ES6 才有.
也就說 Node 4 或者更高的版本才行, 或者瀏覽器代碼用 Babel 編譯一下,
當然能其他的編譯工具可能也行, 但你要確認下是支持 Generator 的.
注: 文章寫得早, 現在翻譯文章, Chrome 應該是支持 Generator 的.
扯多了, 來看例子吧!
例 1: 進程
***個要學的概念是"進程". 進程可以執行代碼, 簡單說就是這樣的了. :)
注: 當然不是操作系統原始的進程了, js 里模擬的.
這是啟動進程的語法: generator 函數作為參數, 傳給 go 函數執行.
- import {go} from 'js-csp';
- go(function* () {
- console.log('something!');
- });
- // terminal output:
- //
- // => something!
例 2: 進程可以暫停
使用 yield 關鍵字可以暫停一個進程, 把當前進程的占用釋放:
- import {go, timeout} from 'js-csp';
- go(function* () {
- yield timeout(1000);
- console.log('something else after 1 second!');
- });
- console.log('something!');
- // terminal output:
- //
- // => something!
- // => something else after 1 second!
例 3: 進程等待來自管道的數據
第二個要學的概念是管道, 也是***一個了. 管道就像是隊列.
一旦進程對管道調用 take, 進程就會暫停, 直到別人往管道放進數據.
- import {go, chan, take, putAsync} from 'js-csp';
- let ch = chan();
- go(function* () {
- const received = yield take(ch);
- console.log('RECEIVED:', received);
- });
- const text = 'something';
- console.log('SENDING:', text);
- // use putAsync to put a value in a
- // channel from outside a process
- putAsync(ch, text);
- // terminal output:
- //
- // => SENDING: something
- // => RECEIVED: something
例 4: 進程通過管道來通信
管道的另一邊, 往管道里 put 數據的那些進程也會暫停, 直到這邊進程調用 take.
下面的例子就復雜一點了, 試著跟隨一下主線, 印證一下終端輸出的內容:
- import {go, chan, take, put} from 'js-csp';
- let chA = chan();
- let chB = chan();
- // Process A
- go(function* () {
- const receivedFirst = yield take(chA);
- console.log('A > RECEIVED:', receivedFirst);
- const sending = 'cat';
- console.log('A > SENDING:', sending);
- yield put(chB, sending);
- const receivedSecond = yield take(chA);
- console.log('A > RECEIVED:', receivedSecond);
- });
- // Process B
- go(function* () {
- const sendingFirst = 'dog';
- console.log('B > SENDING:', sendingFirst);
- yield put(chA, sendingFirst);
- const received = yield take(chB);
- console.log('B > RECEIVED:', received);
- const sendingSecond = 'another dog';
- console.log('B > SENDING:', sendingSecond);
- yield put(chA, sendingSecond);
- });
- // terminal output:
- //
- // => B > SENDING: dog
- // => A > RECEIVED: dog
- // => A > SENDING: cat
- // => B > RECEIVED: cat
- // => B > SENDING: another dog
- // => A > RECEIVED: another dog
例5: 管道也是隊列
由于管道是隊列, 當進程從管道取走數據, 其他進程就拿不到了.所以推數據的是一個進程, 取數據的也是一個進程.
下面這個例子可以看到第二個進程永遠不會打印 B > RECEIVED: dog,
因為***個進程已經把數據取走了.
- import {go, chan, take, put} from 'js-csp';
- let ch = chan();
- go(function* () {
- const text = yield take(ch);
- console.log('A > RECEIVED:', text);
- });
- go(function* () {
- const text = yield take(ch);
- console.log('B > RECEIVED:', text);
- });
- go(function* () {
- const text = 'dog'
- console.log('C > SENDING:', text);
- yield put(ch, text);
- });
- // terminal output:
- //
- // => C > SENDING: dog
- // => A > RECEIVED: dog
例 6: 帶緩沖的管道不會在 put 操作時阻塞
管道可以帶緩沖, 也就是, 一定數量之內的數據, 執行 put 操作可以避開阻塞.
這個例子里, 即便沒有其他進程調用 take, 前兩個寫操作也不會阻塞進程.
不過管道的緩存數量是 2, 所以第三個數據就阻塞進程了, 直到其他進程取走數據.
- import {go, chan, put, buffers} from 'js-csp';
- let ch = chan(buffers.fixed(2));
- go(function* () {
- yield put(ch, 'value A');
- yield put(ch, 'value B');
- console.log('I should print!');
- yield put(ch, 'value C');
- console.log('I should not print!');
- });
- // terminal output:
- //
- // => I should print!
例 7: Dropping And Sliding Buffers
固定大小的緩沖在 N 個數據之后會阻塞, 初次之外, 還有對緩沖的 dropping 和 sliding 控制.
緩沖的 dropping 以為著管道可以持有 N 個數據.再增加額外的數據放進管道, 管道就會將其丟棄.
緩沖的 sliding 也可以持有 N 個數據. 不過相對于直接丟棄新數據,sliding 緩沖原先的***個推的數據會被丟棄, buffer 里會留下新的這個數據.
下面這個例子, value B 和 value C 在 dropping 緩沖里被丟棄, 因為已經有 value A 了.
第二個進程里, 當 value B 被放進管道, value A 就被丟棄了.
然后 value C 放進管道, value B 就被丟棄.
根據它們的工作原理, dropping 和 sliding 的緩沖永遠不會阻塞!
- let droppingCh = chan(buffers.dropping(1));
- let slidingCh = chan(buffers.sliding(1));
- go(function* () {
- yield put(droppingCh, 'value A');
- yield put(droppingCh, 'value B');
- yield put(droppingCh, 'value C');
- console.log('DROPPING:', yield take(droppingCh));
- });
- go(function* () {
- yield put(slidingCh, 'value A');
- yield put(slidingCh, 'value B');
- yield put(slidingCh, 'value C');
- console.log('SLIDING:', yield take(slidingCh));
- });
- // terminal output:
- //
- // => DROPPING: value A
- // => SLIDING: value C
結論
CSP 用了一段時間之后, 用回調或者 Promise 寫代碼就像是侏羅紀的技術.
我希望 ES6 的 Generator 能幫助 CSP 成為 JavaScript 的一個標準,
就像是 Go 已經是的那樣, 以及 Clojure 里正在成為的那樣.
下一步
另外有兩個模型也還有意思, 大概可以認為是比 CSP 層級更高一點的:
函數式也是響應式編程(Rx)跟 Actors, 分別在 Rx 和 Erlang 里用到.
我當然后面也會寫博客來挖掘一下.
我同時相信 CSP 對于前端框架來說非常棒.