Ruby纤程程序中的控制流

3

我知道纤程是协作线程。一个纤程控制着执行上下文,而抢占式线程则不是这样。一个纤程可以放弃控制权,也就是说,一个纤程可以在定义明确的地方开始和停止。

显然,在事件驱动的 Ruby 中使用纤程的原因是为了清理由反应器模式引起的嵌套块。

但我很难理解下面使用纤程的脚本的控制流程。

def http_get(url)
  f = Fiber.current
  http = EventMachine::HttpRequest.new(url).get

  # resume fiber once http call is done
  http.callback { f.resume(http) }
  http.errback  { f.resume(http) }

  return Fiber.yield
end

EventMachine.run do
  Fiber.new{
    page = http_get('http://www.google.com/')
    puts "Fetched page: #{page.response_header.status}"

    if page
      page = http_get('http://www.google.com/search?q=eventmachine')
      puts "Fetched page 2: #{page.response_header.status}"
    end
  }.resume
end

我理解的方式如下:
1)EM启动其事件循环。
2)创建纤维,然后调用resume。传递给new的代码块是立即执行还是在调用resume之后执行?
3)第一次调用http_get。它使用异步事件(在Linux上使用select、poll或epoll)。我们设置异步事件的事件处理程序(在回调方法中)。然后Fiber自愿将控制权交给线程EventMachine所在的线程(主线程)。但是,一旦回调被调用,它就会使用f.resume(http)重新获得控制权。但在这个简化的例子中,我应该在f.resume(http)之后放置自己的回调代码吗?因为现在看起来f.resume(http)只是返回控制权到纤维并没有做其他事情。
我认为在yield之后控制流转到EventMachine,然后进入其事件循环中。所以第二个http_get还没有被调用。现在一旦回调被调用,控制就会返回到Fiber(我们只使用一个Fiber.new,所以我假设在所有这些中只有一个Fiber实例)。但第二个http_get何时被调用?

注意:当创建一个Fiber时,它不会自动运行。相反,必须使用Fiber#resume方法显式地要求它运行。 - Daniel Viglione
1个回答

1

让我看看能不能为您回答。我会添加行号来帮助描述:

01: def http_get(url)
02:   f = Fiber.current
03:   http = EventMachine::HttpRequest.new(url).get
04: 
05:   # resume fiber once http call is done
06:   http.callback { f.resume(http) }
07:   http.errback  { f.resume(http) }
08: 
09:   return Fiber.yield
10: end
11: 
12: EventMachine.run do
13:   Fiber.new{
14:     page = http_get('http://www.google.com/')
15:     puts "Fetched page: #{page.response_header.status}"
16: 
17:     if page
18:       page = http_get('http://www.google.com/search?q=eventmachine')
19:       puts "Fetched page 2: #{page.response_header.status}"
20:     end
21:   }.resume
22: end

  1. 第21行开始执行Fiber,其代码在第14-20行。
  2. Fiber代码似乎在做以下事情:第14行检查是否可以在google.com上进行GET请求。在第17行,它检查是否有来自http_get的有效响应,然后在第18行执行下一个请求以搜索字符串eventmachine
  3. 当由第21行的.resume引起Fiber执行时,将执行第14行,该行调用http_get方法。
  4. 第02到07行设置异步HTTP GET请求和回调。
  5. 第09行将控制权交还给EventMachine。
  6. 一段时间后,当来自第03行的异步HTTP GET调用完成执行并导致第06或07行中的一个回调结果时,原始的Fiber(创建于第13到20行)将重新获得控制权。
  7. 现在Fiber执行从第15行恢复。第06/07行的回调传递了对http对象的引用,现在在第14行中使用变量page引用该对象,并在随后的第15行中用于转储HTTP请求状态。
  8. 随着Fiber继续执行,它检查page是否为真值,然后继续调用http_get,但使用新的URL。请注意,如果pagenil,则代码if page可能永远不会执行,因为第15行在没有检查nil的情况下访问了page
  9. 重复类似的过程-第02到07行设置HTTP GET调用,第09行将控制权交还给EventMachine。
  10. 一段时间后,其中一个回调被调用,Fiber重新获得控制,并执行第19行。
  11. 执行第19行后,Fiber将死亡。
希望这能澄清事情的问题。
关于处理HTTP GET响应和额外逻辑的部分,我想你可以用一些有意义的处理逻辑来替代puts。在这个示例中,puts似乎正在处理响应,而回调主要用于恢复Fiber。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接