【问题标题】:Is Scala Truly AsynchronousScala 真的是异步的吗
【发布时间】:2015-07-30 17:57:28
【问题描述】:

这些天我正在研究 Scala 以编写一个非阻塞的 Restful API。我已经在 node.js 中完成了几个项目,这次我很想研究 Scala。

到目前为止,我的发现是,在 Scala 中编写 100% 异步代码是不可能的,因为大多数底层代码都是阻塞的。您可以找到有关如何在 Scala 应用程序中使用 JDBC 进行数据库连接的教程。 JDBC 被阻塞意味着任何使用它的应用程序都不能在单线程环境中工作,因为它会在等待 DB 响应时将所有其他请求排队。Play 框架建议在使用阻塞 API 时增加线程池,这意味着我们将从哪里返回我们开始了。

对于 node.js,几乎所有可用的模块都是异步编写的。特别是所有数据库连接器和文件处理程序都是异步读/写的,请记住,当您只有一个线程时,没有任何东西可以阻塞。然而,我看到人们更喜欢 Scala 而不是 node.js,因为它的类型安全性和计算能力,同时是异步的。但是异步行为并没有反映在大多数在线可用的教程和资源中。

我的问题是,只有我没有理解这一点,还是在 JVM 世界中对异步的实际含义存在混淆?

更新:

我知道 JDBC 同步不会使 Scala 同步,但我看到有人争辩说他们的 API 是非阻塞的,但他们使用的是同步库。此外,如果大多数驱动程序和库仍然是阻塞的,如何在 Scala 中编写非阻塞应用程序。即使有替代方案,这也意味着在使用库检查其内部结构时必须非常小心,看看它是阻塞还是非阻塞,而 node.js 不是这种情况。

【问题讨论】:

  • 在这个问题中 JDBC 与 Scala 是异步的存在混淆。
  • Scala 或 Node.js 本质上都不是异步的,但它们的库是异步的。考虑到 JDBC,作为同步的低级别,但上面有异步库(Slick,Anorm)。
  • 是的 JDBC 是同步的,但我看到了编写非阻塞服务器的教程,但他们正在使用 JDBC 或以阻塞方式处理文件。
  • 非阻塞(在 Typesafe 意义上)== 线程没有被非活动进程占用。没有等待或睡眠。相反,该线程正在分配给目前需要它的人。
  • 是的,但是如果存在的唯一线程对 DB 进行查询并等待响应,则没有其他线程可以处理其他请求。在这种情况下,线程很可能处于等待状态。

标签: node.js scala asynchronous java-8 nonblocking


【解决方案1】:

用 Scala(和 Java)编写异步应用程序是可能的(我什至会说很容易)但你必须小心第三方库,知道哪些 API:s阻塞和异步。

例如,Java 标准库提供了阻塞和非阻塞 IO API。

JDBC 是另一个固有阻塞 API 的常见示例,如果您想编写 100% 异步应用程序,您只需为您的数据库使用不同的客户端。

与 node.js 的一大区别在于,您实际上可以处理阻塞 API:s 和异步内容。这是可能的,因为我们在 JVM 中有多线程,并且可以在为该任务调整的单独线程池中运行阻塞的东西。因此,如果我们有 8 个可能的阻塞数据库连接,我们可以在具有 8 个线程的线程池上运行这些连接,而不会阻塞“常规”线程池上的任何异步代码。

附带说明,我们具有并发性还允许我们每个内核有一个线程来并行运行异步代码,而不是一个用于整个 vm。

【讨论】:

  • 这让我很害怕,因为现在我不得不担心我的应用程序的哪些部分需要单独的线程池,因为它们是阻塞的,特别是如果我还没有编写库。这也意味着我需要进行严格的并发测试,以查看应用程序的每个组件在负载下的行为,特别是如果任何数据源停止响应甚至很短的时间。
  • 我假设您在这里隐式比较 JVM 和 Node.js?您的观点是正确的,但比较不是:问题是 Node.js 只有一个实际线程 - JVM 可以拥有系统需要的任意数量。所以 JVM 让事情变得更复杂,但这是因为它更强大。
【解决方案2】:

在 Scala 中编写完全异步的代码绝对是可能的。有很多响应式库,TypeSafe 为提供响应式平台/生态系统付出了很多努力。要说服自己,只需查看 awesome scala 并搜索“Reactive”,或关注 TypeSafe webinars。

我不知道Node.js,但我认为你可以编写这样的阻塞函数:

function aBlockingFunction() {
  while(resourceNotAvailable) {
  }
  ...
}

Scala 不会神奇地使同步代码异步,但它会为您提供编写完全异步代码所需的一切(Futures/Promises、Actors、Streams...)。

异步并不意味着单线程,它意味着你在等待回复的同时不断地做有用的思考。 Play 被设计为完全异步的,但您仍然可以编写阻塞代码(例如调用同步 API)。但是,如果您编写完全异步的,是的,您可以运行单个线程。为此,只需使用 Futures/Actors/...

Scala 并发是这样建模的:有一个(实际上很多)线程池对应于并行执行,并且有任务要做(Futures/Actors/...)。任务在可用线程上分派。当一个任务需要等待回复或yield时,线程被分配给另一个准备好的任务,依此类推。您看似“单线程”的计算实际上是由许多小型协调迷你任务组成的,这些任务在可用的线程上运行(通常不相同)。

默认情况下,Play thread pools have as many threads as available processors。这是完美的,因为您的代码是异步的,因为线程永远不会空闲(当一个任务空闲时,另一个任务会替换它)。但是在执行阻塞操作时,您会阻塞胎面,因此您将少一个处理器,直到操作完成。

这是众所周知的合作/先发制人问题。 Co-operative 更轻更快,因为它不需要保存那么多上下文(它可以像函数调用一样轻),但是一个任务可以阻塞其他任务。

这就是为什么在使用大量阻塞调用时,Play 建议增加线程池(您实际上应该创建另一个专门用于阻塞操作的线程池)。理想情况下,您应该分配“number_of_simultaneous_blocking_operations + number_of_possible_parallel_executions”线程,以便始终有一个线程可用于就绪任务。

我的建议是:使用异步机制(Futures/Actors/Streams/...)和响应式库。

【讨论】:

    【解决方案3】:

    Scala 构建在 JVM 之上,而 JVM 世界中的大部分代码(包括 JDBC)都是为 Java 编写的。 Scala 试图鼓励您使用 Futures 或 Akka Actors 编写非阻塞代码,但显然就像在任何编程语言中一样,您可以随心所欲。

    所以对于您的问题,没有 scala 不是 100% 非阻塞的,我的意思是如果您愿意,您甚至可以使用 Java 中的同步块。但是我们鼓励并考虑考虑阻塞代码的缺点以及如何避免它(不可变、无状态等),并且支持您通过语言中的功能编写非阻塞代码(用于转换数据而不是更改数据的单子)一种可变的方式)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-25
      • 2014-09-19
      • 1970-01-01
      • 1970-01-01
      • 2019-11-30
      • 2020-03-26
      • 1970-01-01
      相关资源
      最近更新 更多