当前位置:首页 > 公众号精选 > 码农的荒岛求生

作为计算机早期的一名软件工程师,你面临一个棘手的问题。

这一时期操作系统中已经发明了线程,线程非常好用,因此快速流行起来。

但在一些特定场景下线程也有自己的问题,尤其是后端服务器。

大家都会创建一个线程来处理用户请求,如果在此期间发起一个阻塞调用(比如调用下游服务),那么整个线程都会被挂起,在此期间,线程不能执行任何其他工作:

get_response();  // 线程被阻塞
parse_response();

在高并发场景下,这需要创建大量线程,导致巨大的线程创建/切换开销和内存消耗。

这就是最开始的同步编程模型,它简单、直观,但并不高效。回调函数与异步编程

为了解决同步编程模型低效的问题你发明了一个简单而优雅概念:回调函数,callback。

线程不再原地阻塞等待操作完成,而是提供一个函数,告诉系统"当操作完成时,调用这个函数"。

get_response(parse_response);  // get_response直接返回

这里,parse_response就是回调函数,意思是得到下游的response后用parse_response来处理结果,这样get_response会立刻返回不会阻塞线程,parse_response会在其它线程中被执行。

至此,你发明了异步编程。

利用异步编程你不必创建大量线程就能高并发处理请求。

但随着项目复杂度增加,你开始遇到新的问题。回调地狱:噩梦的开始

当你需要执行一系列依赖的异步操作时,回调函数开始变得难以管理。例如,你需要先获取用户信息,然后获取用户的订单,最后获取订单的详细信息:

getUser(userId, function(user) {
  getUserOrders(user.id, function(orders) {
    getOrderDetails(orders[0].id, function(details) {
      displayOrderDetails(details);
    }, function(error) {
      handleOrderDetailsError(error);
    });
  }, function(error) {
    handleOrdersError(error);
  });
}, function(error) {
  handleUserError(error);
});

这种代码结构被形象地称为"回调地狱"(Callback Hell),回调函数本身会切割处理逻辑,而随着嵌套层级的增加,代码变得越来越难以阅读和维护。

使用回调函数实现复杂的控制流(如并行执行多个异步操作,或者有条件地执行异步操作)非常困难,你需要新的异步编程范式。

经过反复思考,你意识到问题的核心在于:回调函数并没有优雅的以线性方式组合异步操作。

该怎么解决这个问题呢?未来还是现在?

很简单,再来一层抽象,这种抽象需要创造一种“时间容器”,将“值”与“计算过程”分离,然后以链式调用的方式编排异步操作。

这种抽象就是promise/future。

Promise(或在某些语言中称为Future)。这是一个代表"未来某个时刻会有结果"的对象,但是现在你可以基于这个对象进行各种操作, Promise最强大的特性是它支持链式调用,通过.then()方法,你可以指定当未来的结果到来时要执行的操作;通过.catch()方法,你可以处理Promise失败的情况。

getUser(userId)
  .then(user => getUserOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => displayOrderDetails(details))
  .catch(error => handleError(error));

这种链式结构成功的实现了以线性方式组合异步操作,它没有回调函数那样的深度嵌套。

除了链式调用,Promise还提供了强大的组合功能,例如Promise.all()可以并行执行多个Promise,当所有Promise都成功时返回所有结果的数组。

    这些组合方法让你能够轻松处理复杂的并发场景,这在回调模式中是极其困难的。同步加异步

    Promise彻底改变了你处理异步代码的方式。代码结构变得更加清晰,错误处理更加集中,控制流更加灵活。你终于摆脱了回调地狱的噩梦。

    然而,随着你使用Promise的时间增长,你开始注意到一个新的问题:虽然Promise解决了嵌套问题,但它仍然基于回调机制(.then()和.catch()方法本质上是注册回调函数),对于特别复杂的异步逻辑,代码仍然不够直观。

    实际上,promise/future说到底只是一种伪同步代码风格而已,开发者仍需在“回调式”思维和“伪同步”代码之间转换。

    那么有可能把同步编程的直观和异步编程的高效结合起来吗?

    你意识到要想用同步编程来实现异步编程的高效就不能在发起阻塞操作时真的阻塞线程。

    怎么做到这一点呢?显然你需要在线程的基础上实现自己的控制流,至此,协程诞生了。。。

    本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
    关闭