Javascript协程与迭代器

3,850 阅读3分钟

什么是协程

协程概念最早起源于如何解决COBOL语言编译器OnePass编译的问题[1],其后即使在结构化编程思想一统天下的时代,GOTO[2]的支持也是在取舍之间。 其本质就是控制流主动让出和恢复机制,让控制流更加流畅,而相对于抢占式的调度方式来说,往往还需要CPU底层支持,当然目前抢占式仍然是主流。

协程、线程区别和联系

协程 线程 进程
独立上下文 Y Y Y
切换 自主(用户) 系统调度 系统调度
消耗资源
共享堆 Y Y N
共享栈 N N N
用户态到核心态[3] N Y Y

而协程与线程主要的区别有两个,最大的就是调度方式,线程是操作系统调度,协程是应用系统自己调度。 另外一个区别,协程的栈空间是可以动态调整的,这样空间利用率就可以更高,一个任务需要2K空间就分配2K内存,一个任务需要20M空间就分配20M,而不用担心栈空间不够或者空间浪费。[4]

协程是线程之下的更轻量级的表现,也称为轻量级线程,协程可以更好的利用CPU,并且可以更好的利用内存[4:1].

协程和迭代器

协程的实现往往和迭代器密不可分,如JS、Python、C#等。 迭代器设计模式[5]是一种提供顺序访问一个聚合对象中的各种元素,而又不暴露内部细节的一种方式。 协程通过使用yield关键字来暂停自身,直到被输入一个值,然后执行下一个yield执行语句,有的人称JS的协程为浅协程[6].

协程是无限制的协作式多任务任务:在协程内部,任何函数都可以挂起整个协程(函数激活本身,函数调用方的激活,调用方的调用方的激活等)。但是JS只能直接从生成器内部挂起生成器,而只有当前函数激活被挂起。由于这些限制,生成器有时被称为浅协程

例如:

function coroutine(f) {
    var o = f(); // instantiate the coroutine
    o.next(); // execute until the first yield
    return function(x) {
        o.next(x);
    }
}

var test = coroutine(function*() {
    console.log('Hello!');
    var x = yield;
    console.log('First I got: ' + x);
    var y = yield;
    console.log('Then I got: ' + y);
});
// prints 'Hello!'

test('a dog'); ​// prints 'First I got: a dog'
test('a cat'); // prints 'Then I got: a cat

进化: async & await

ES7带来的异步编程解决方案,是对yield的'复杂'而又隐藏细节的包装[7]。 参考例如如下[8]

// ES6 Generattor
// Generators
function request(url) {
    ajax(url, (response) => {
        iterator.next(JSON.parse(response));
    });
}

function *main() {
    // 获取产品数据
    let data = yield request('products.json');

    // 获取用户数据
    let users = yield request('users.json');

    // 获取评论数据
    let products = yield request('comments.json');

    console.log('Generator/products >>>', products);
    console.log('Generator/users >>>', users);
    console.log('Generator/comments >>>', comments);
}

var iterator = main();
iterator.next();
// ES7 Async&Await
// 封装 Ajax,返回一个 Promise
function requestP(url) {
    return new Promise(function(resolve, reject) {
        ajax(url, (response) => {
            resolve(JSON.parse(response));
        });
    });
}

(async () => {
    // 获取产品数据
    let data = await requestP('products.json');

     // 获取用户数据
    let users = await requestP('users.json');

     // 获取评论数据
    let products = await requestP('comments.json');

    console.log('ES7 Async/products >>>', products);
    console.log('ES7 Async/users >>>', users);
    console.log('ES7 Async/comments >>>', comments);
}());

不同语言中的协程

Golang: goroutine, yield, resume Java: goto Python: yield, resume[9] Lua: yield, create, resume C: ucontext JS: yield C#: yield, IEnumerable

总结

协程存在的历史,不亚于进程的概念兴起,直至今日才广受关注有着非结构化语言的弊病(goto之争),也有自上而下设计体系的原因,但总归由于其对异步表达的优异性和轻量化又重新回到了程序设计的视野当中,新壶装旧酒,对于很多概念而言并不是凭空出现,往往跟多的是组合新生。

参考资料


  1. www.360doc.com/content/17/… ↩︎

  2. c2.com/cgi/wiki?St… ↩︎

  3. www.jianshu.com/p/6dde7f929… ↩︎

  4. www.imooc.com/article/317… ↩︎ ↩︎

  5. baike.baidu.com/item/迭代器模式/… ↩︎

  6. zhangchen915.com/index.php/a… ↩︎

  7. dkandalov.github.io/async-await ↩︎

  8. github.com/jaydson/es7… ↩︎

  9. zhuanlan.zhihu.com/p/30275154 ↩︎