Do You Test Ruby Code for Thread Safety?

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

你是一个Ruby开发人员吗?如果是的话,我相信你对并发性和线程安全性有一个非常模糊的概念。不要误会,这是我在过去半年中处理Ruby代码并与Ruby程序员交流后得出的结论。最近我一直在积极地使用Ruby编写代码,并且我喜欢这门语言以及其周围的生态系统。我们正在创建的实验性加密货币Zold几乎完全是用Ruby编写的。这告诉你什么?我喜欢Ruby。但是当涉及到并发性时,有很多空白。相当严重。

看看这个Ruby类:

这是一个简单的网络服务器。它会工作—尝试像这样运行它(您需要安装 Ruby 2.3+):

然后,打开http://localhost:4567,你将会看到计数器。刷新页面,计数器将会增加。再试一次。它有效。计数器是在文件idx.txt中,它本质上是一个全局变量,我们在每个HTTP请求上递增它。

让我们为它创建一个单元测试,以确保它被自动测试。

好的,这不是一个单元测试,更像是一个集成测试。首先,我们在一个后台线程中启动一个web服务器。然后我们等待一秒钟,以给予该线程足够的时间来引导服务器。我知道,这是一个非常丑陋的方法,但对于这个小例子我没有更好的办法。接下来,我们发送一个HTTP请求,并将其与预期的数字1进行比较。最后,我们停止web服务器。

到目前为止还不错。现在的问题是,当发送多个请求到服务器时会发生什么?它是否仍然会返回正确的连续数字?让我们试试看:

在这里,我们发出一千个请求,并将所有返回的数字放入一个数组中。然后,我们对数组进行uniq操作,并对其元素进行count操作。如果一共有一千个元素,说明一切正常,我们收到了一个正确的连续且唯一的数字列表。我刚刚测试过它,它有效。

但是我们是逐个生成它们的,这就是为什么我们的服务器没有任何问题。我们不是并发生成它们的。它们严格按照顺序一个接一个生成。让我们尝试使用一些额外的线程来模拟HTTP请求的并行执行:

首先,我们将数字列表保存在Concurrent::Set中,它是Ruby Set的线程安全版本。其次,我们启动五个后台线程,每个线程发起200个HTTP请求。它们都并行运行,并通过在每个线程上调用join来等待它们完成。最后,我们从Set中取出数字,并验证列表的正确性。

毫无疑问,它失败了。

当然,你知道为什么。因为这个实现不是线程安全的。当一个线程正在读取文件时,另一个线程正在写入文件。最终,它们会很快发生冲突,文件的内容就会损坏。我们在测试中放入的线程越多,结果就越不准确。

为了使这种类型的测试更容易,我创建了threads,一个简单的Ruby gem。它的工作原理如下:

就是这样。这一行代码Threads.new()代替了所有其他代码行,我们在其中必须创建线程,确保它们同时开始,然后收集它们的结果,并确保如果它们崩溃,它们的堆栈跟踪在控制台中可见(默认情况下,后台线程的错误日志是不可见的)。

在你的项目中尝试这个宝石吧,它已经经过了相当充分的测试,我在所有并发测试中都使用它。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 14:02

sixnines availability badge   GitHub stars