我正在调查旧版 Rails 应用程序中的一些瓶颈。除其他外,它使用以下类的对象作为 Controller 响应主体向(防火墙)后端服务器发出一些 HTTP 请求并将响应流式传输到客户端:
class Streamer
def initialize(url)
@url = url
end
def each
client = HTTPClient.new
client.receive_timeout = 7200
client.send_timeout = 3600
client.connect_timeout = 7200
client.keep_alive_timeout = 3600
client.get_content(@url) { |chunk|
yield chunk
}
end
end
我是 Ruby I/O 和线程方面的新手,而且我也不是 Rails 方面的专家。设计假设似乎是这个(在 MRI 上运行)只会在每个 block 中锁定解释器一次,并且当数据来自
HTTPClient
时其他线程可以执行。 ,或者去浏览器——这个假设有效吗?还是这段代码会让 Puma 的其他线程饿死?
最佳答案
答案是肯定的和否定的,主要是因为问题中的术语有些不清楚。
阻塞 IO 表示 IO 正在等待数据。 HTTPClient
使用阻塞 IO。它等待数据可用,只有在收到数据后才返回。
非阻塞 IO 意味着 IO 层立即返回,即使没有数据存在(通常带有错误 EAGAIN 或 EWOULDBLOCK)。HTTPClient
模块阻塞控制流,等待 HTTP 服务器的响应。
但是,这不会阻止其他线程“并行”运行(或者,对于 Ruby MRI,交叉运行)。
The design assumption seems to be that this (running on MRI) is only going to lock the interpreter once per chunk, and that other threads can execute while data is either coming in from the HTTPClient, or going out to the browser -- is that assumption valid?
一般来说,假设是正确的(或足够接近以至于无关紧要)。
Ruby MRI 中的 GIL(全局解释器锁)强制 Ruby 代码执行单线程。它阻止了真正的并行性,让人想起单核 CPU 机器使用的多线程模型。
但是,由于 IO 代码在 GIL 之外执行,其他线程将在 IO 等待传入数据时运行(阻塞
write
、 read
或 select
)。通常,Ruby 将管理线程调度以交错 Ruby 线程执行,允许并发执行(尽管不是并行执行)。
Or is this code going to starve Puma's other threads?
不知道你说的饿死是什么意思...
该代码将饿死 Puma 的线程 游泳池 ,但线程本身将继续不受阻碍地运行。
澄清:
Puma 有一个用于 HTTP 请求和任务处理的线程池。线程数是有限的。
HTTPClient 线程将被“卡住”,等待 IO 完成并且不会返回线程池,从而减少池(作为一个整体)并可能导致资源匮乏。
例如,如果所有线程都在等待 HTTPClient 响应,则在线程再次可用之前不会处理任何请求(HTTPClient 和其余工作完成)。
另一方面,线程本身将允许其他线程同时运行,使用交错调度。所以,其他线程不会饿死,但 Puma 整体可能会饿死。
关于multithreading - 这个 Ruby 代码会在 Puma 下使用非阻塞 I/O 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51029903/