IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [译] 快速介绍 JavaScript 中的 CSP

    题叶发表于 2016-09-10 23:23:49
    love 0

    原文 http://lucasmreis.github.io/b...

    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 对于前端框架来说非常棒.

    原作者还有一个文章可以看下: Using CSP as Application Architecture



沪ICP备19023445号-2号
友情链接