现代编程语言中的并发性:Rust、Go、Java、Node.js、Deno、.NET 6

2025-05-24

现代编程语言中的并发性:Rust、Go、Java、Node.js、Deno、.NET 6

最初发表于deepu.tech

这是一个由多个部分组成的系列文章,我将在其中探讨现代编程语言中的并发性。我将受《Rust》一书中的示例启发,使用 Rust、Go、JavaScript(NodeJS)、TypeScript(Deno)、Kotlin 和 Java 等热门语言构建并测试一个并发 Web 服务器,以比较这些语言/平台之间的并发性及其性能。本系列的章节如下。

  1. 介绍
  2. Rust 中的并发 Web 服务器
  3. Golang 中的并发 Web 服务器
  4. 使用 NodeJS 的 JavaScript 并发 Web 服务器
  5. 使用 Deno 实现 TypeScript 的并发 Web 服务器
  6. 使用 JVM 的 Java 并发 Web 服务器
  7. 基准测试的比较和结论

什么是并发

并发是编程中最复杂的方面之一,并且根据您选择的语言,其复杂性可以是从“看起来很令人困惑”到“这是什么黑魔法”。

并发是指多个任务可以在重叠的时间段内以任意特定顺序执行,且不会影响最终结果的能力。并发是一个非常宽泛的术语,可以通过多线程、并行和/或异步处理来实现。

并发

首先,我建议您阅读介绍文章以更好地理解这篇文章。

基准测试与比较

在之前的博文中,我用 Rust、Go、Node.js、Deno 和 Java 构建了一个简单的 Web 服务器。我尽可能地简化了代码,避免使用外部依赖项。此外,我还保持了不同语言的代码相似性。在最后一篇博文中,我们将比较所有这些实现的性能,看看哪种语言能够为并发 Web 服务器提供最佳性能。

如果该语言同时支持异步和多线程并发,我们将尝试两者或两者的组合,并选择性能最佳的进行比较。因此,应用程序的复杂性将取决于语言特性和语言复杂性。我们将使用该语言提供的任何功能,尽可能提高并发性能,而不会使事情过于复杂。Web 服务器将只服务于一个端点。 ,并且每十个请求就会增加两秒的休眠时间。在我看来,这会模拟更真实的负载。

如果需要并且语言支持,我们将使用 Promise、线程池和 Worker。我们不会在应用程序中使用任何不必要的 I/O。

代码实现可能并非最佳;如果您有任何改进建议,请在此仓库中提交 issue 或 PR 。进一步的改进可能包括:

  • 使用线程池实现 Java 多线程版本
  • 使用 Java Web 服务器库
  • 用于createReadStreamNode.js
  • 使用 Warp、Rocket 或 actix-web 来处理 Rust添加了 Rust actix-web 示例以供比较

免责声明:我并非声称这是一种精确的科学方法,或是并发性能的最佳基准测试。我确信不同的用例会得出不同的结果,而且现实世界的 Web 服务器会更加复杂,需要在并发进程之间进行通信,从而影响性能。我只是想针对一个简单的用例提供一些简单的基础比较。此外,我对某些语言的了解程度可能高于其他语言,因此我可能会遗漏一些优化。所以请不要对我大喊大叫。如果您认为某种语言的代码可以开箱即用地改进以提高并发性能,请告诉我。如果您认为这个基准测试毫无用处,那么请推荐一个更好的基准测试 :)

更新:尽管有上述免责声明,但人们仍然对我使用thread.sleepApacheBench 模拟阻塞并进行基准测试感到不满。我已经更新了帖子,添加了更多使用不同工具的基准测试。这仍然不科学,也不是基准测试并发性的最佳方法。这只是我的一些实验。如果您有更好的想法,请随时使用代码并发布后续文章或评论,并附上您的结果,我会将其更新到帖子中并注明您的贡献。

本次比较中使用的所有实现都可以在该 GitHub 存储库的 nosleep 分支中找到。

基准测试条件

这些是我将用于基准测试的一些条件。

  • 我们使用了可用的语言/运行时的最新稳定发布版本,截至撰写本文时,这些版本包括:
    • 锈:1.58.1-Stable
    • 去:1.17.6
    • Java:OpenJDK 17.0.2
    • Node.js:17.4.0
    • 德诺:1.18.1
    • 。网:6.0.100
  • 更新:Thread.sleep 已从所有实现中删除。
  • 仅当这是语言中标准推荐的方式时,我们才会使用外部依赖项。
    • 将使用截至撰写本文时的此类依赖项的最新版本
  • 我们不会考虑使用任何配置调整来提高并发性能
  • 更新:很多人指出 ApacheBench 并不是这项基准测试的最佳工具。因此,我还附上了wrkdrill的结果。
  • 我们将使用 ApacheBench 进行基准测试,其设置如下:
    • 100 个请求的并发因子
    • 总共 10000 个请求
    • 每种语言的基准测试将进行十次,包括预热轮,然后使用平均值。
    • Fedora 上的 ApacheBench 版本:httpd-tools-2.4.52-1.fc35.x86_64
    • 使用的命令:ab -c 100 -n 10000 http://localhost:8080/
  • 所有基准测试均在同一台机器上运行,该机器运行 Fedora 35,配备 Intel i9-11900H(8 核/16 线程)处理器和 64GB 内存。
    • wrk客户drill端都是从同一网络上的另一台类似机器和同一台计算机运行的;结果大致相同;我使用客户端计算机的结果进行比较。

比较参数

我还将比较以下与并发相关的方面。

  • 基于基准测试结果的性能
  • 社区共识
  • 易于使用和简单性,特别是对于复杂的用例
  • 并发的外部库和生态系统

基准测试结果

更新:我根据wrkdrill 的结果更新了基准测试结果,并且在根据各位朋友的建议进行调整后,更新了 ApacheBench 的先前结果。

更新 2 :感谢srollinet的PR ,代码库中现已新增.NET 6 版本。基准测试已根据 .NET 结果更新。

更新 3:使用 actix-web 和 Java undertow 的 Rust 现已包含在基准测试中wrkdrill这些实现被简化为仅返回字符串,而不是执行文件 I/O,因此它们作为单独的集合显示。我开始将这个系列作为语言并发性的实验。现在,这感觉像是 Web 服务器框架的基准测试;虽然并发性是这些框架的一个重要方面,但我不确定结果是否从语言并发性的角度来说明什么。

来自wrk的结果

wrk使用以下命令进行基准测试(线程 8、连接 500、持续时间 30 秒):



wrk -t8 -c500 -d30s http://127.0.0.1:8080


Enter fullscreen mode Exit fullscreen mode

使用 Go HTTP 版本的 wrk 基准测试

Go HTTP、Rust actix-web、Java Undertow 和 .NET 6 的更新比较

使用 Web 服务器进行 wrk 基准测试

在每秒请求数性能方面,Go、Rust 和 Java Web 服务器版本的表现远超其他版本。如果我们去掉每秒请求数,我们就能得到如下更好的结果。

不使用 Web 服务器的 WRK 基准测试

钻探结果

使用drill并发 1000 和 100 万个请求进行基准测试

钻头基准 1

Go HTTP、Rust actix-web、Java Undertow 和 .NET 6 的更新比较

使用 Web 服务器进行钻取基准测试 1

drill使用2000 并发和 100 万个请求进行基准测试

钻头基准

Go HTTP、Rust actix-web、Java Undertow 和 .NET 6 的更新比较

使用 Web 服务器进行 Drill 基准测试 2

先前的 ApacheBench 线程阻塞测试结果

十次基准测试运行中每十个请求的不同指标的平均值thread.sleep如下:

Apache 平均基准测试

您可以在GitHub 仓库中找到所有使用的结果

结论

根据基准测试结果,以下是我的观察结果。

基准观察

由于基于基准的建议是热门话题,我只会分享我的观察,您可以自己做出决定。

  • 对于使用的 HTTP 服务器基准测试wrk,Go HTTP 在请求/秒、延迟和吞吐量方面胜出,但它比 Rust 占用更多内存和 CPU。这可能是因为 Go 拥有最好的内置 HTTP 库之一,并且经过了极度调优以获得最佳性能;因此,将其与我为 Java 和 Rust 做的简单 TCP 实现进行比较是不公平的。但你可以将它与 Node.js 和 Deno 进行比较,因为它们也有这里用于基准测试的标准 HTTP 库。更新:我现在将 Go HTTP 与 Rust actix-web 和 Java Undertow 进行了比较,令人惊讶的是 Undertow 的表现更好,actix-web 位居第二。可能像 Gin 这样的 Go web 框架会更接近 Undertow 和 actix-web。
  • Go TCP 版本与 Rust 和 Java 实现相比是一个公平的比较,在这种情况下,Java 和 Rust 都胜过 Go,因此可以合理地预期 Rust 和 Java 中的第三方 HTTP 库可以与 Go 竞争,如果我是一个赌徒,我会打赌有一个 Rust 库可以胜过 Go。
  • 资源使用情况则完全不同,Rust 似乎在所有基准测试中始终使用最少的内存和 CPU,而 Java 使用最多的内存,Node.js 多线程版本使用最多的 CPU。
  • 异步 Rust 的表现似乎比多线程 Rust 实现更差。
  • 在使用的基准测试中drill,异步 Java 版本的表现优于 Rust,这让我感到惊讶。
  • Java 和 Deno 的失败请求比其他的要多。
  • 当并发请求数从 1000 增加到 2000 时,大多数实现的失败率都非常高。Go HTTP 和 Rust Tokio 版本的失败率接近 100%,而多线程 Node.js 的失败率最低,在该并发级别上性能良好,但 CPU 使用率较高。它运行多个版本的 V8 引擎进行多线程处理,这解释了 CPU 使用率高的原因。
  • 总体而言,Node.js 的表现似乎仍然优于 Deno。
  • 另一个重要的要点是,像 ApacheBench、wrk 或 drook 这样的基准测试工具似乎会提供非常不同的结果,因此微基准测试不如最终性能基准测试可靠。根据实际用例和具体实现细节,可能会存在很大差异。感谢Eamon Nerbonne 指出这一点
  • Apache 基准测试在启用和禁用该功能的版本上运行的thread.sleep结果并无太大区别,因为所有实现的结果都差不多,这可能是由于 ApacheBench 工具本身的限制。因此,正如许多人指出的那样,我忽略了它们。

要了解更全面的 Web 框架基准测试,我建议查看 TechEmpower 的Web 框架基准测试

使用 ApacheBench 测试,您可以看到,对于存在大量线程阻塞的系统,在处理 10k 个请求的总时间方面,不同语言之间没有显著差异。这意味着在实际用例中,语言选择不会成为并发性能的重大影响因素。当然,如果您追求最佳性能,Rust 显然比其他语言更快,因为它拥有最高的吞吐量,其次是 Java 和 Golang。JavaScript 和 TypeScript 落后于它们,但差距并不大。使用内置 HTTP 服务器的 Go 版本是所有语言中速度最慢的,原因是运行过程中性能不一致,这可能是由于垃圾回收 (GC) 的介入导致峰值。多线程和异步方法之间的差异也很有趣。对于 Rust,多线程实现的性能略胜一筹,而 Java 和 JavaScript 的异步版本的性能略胜一筹。但这些差异都不足以成为在这种特定情况下推荐某种方法的依据。但总的来说,如果可用的话,我建议使用异步方法,因为它更灵活,而且没有线程可能遇到的一些限制。

社区共识

关于并发性能,社区共识分歧很大。例如,Rust 和 Go 社区都声称自己是并发性能最好的。就我个人经验而言,我发现它们的性能比较接近,Rust 略微领先于 Go。Node.js 生态系统建立在异步并发性能的承诺之上,并且有证据表明,切换到 Node.js 后性能会大幅提升。Java 也拥有在实际项目中毫无问题地处理数百万并发请求的丰富经验;因此,很难在这里站队。

另一个普遍的观察是,Rust 在运行过程中的性能非常一致,而所有其他语言都存在一些差异,尤其是在 GC 启动时。

简单

虽然性能很重要,但易用性和简洁性也很重要。我认为区分异步和多线程方法也很重要。

异步:我个人认为 Node.js 和 Deno 是异步并发最简单易用的平台。Golang 是我的第二选择,因为它同样简单易用,功能和性能都不会受到影响。Rust 紧随其后,因为它功能更多,需要一些时间来适应,因此更复杂一些。我会把 Java 排在最后,因为它需要更多样板代码,而且进行异步编程比其他语言更复杂。我希望 Loom 项目能够解决 Java 的这个问题。

多线程:对于多线程并发,我会优先考虑 Rust,因为它功能丰富,而且由于内存和线程安全,在 Rust 中实现多线程非常轻松且无忧。你不必担心竞争条件之类的问题。我会把 Java 和 Go 放在第二位。Java 拥有成熟的多线程生态系统,使用起来也不太难。Go 非常容易使用,但你对操作系统线程的控制能力有限,否则我会给 Go 更高的评价。最后,Node.js 和 Deno 也支持多线程,但它们不如其他语言灵活;因此我把它们放在最后。

生态系统

我认为 Rust 拥有最佳的并发生态系统,其次是 Java 和 Golang,它们拥有成熟的选择。Node.js 和 Deno 虽然不如其他语言,但也提供了不错的生态系统。


如果您喜欢这篇文章,请点赞或留言。

您可以在TwitterLinkedIn上关注我。

文章来源:https://dev.to/deepu105/concurrency-in-modern-programming-languages-rust-vs-go-vs-java-vs-nodejs-vs-deno-36gg
PREV
使用 Zsh 在 Unix 上配置一个漂亮的终端
NEXT
2020 年值得学习的 7 种语言,附赠免费资源