这个Ruby代码是否正确使用了线程,线程池和并发性

和卢比奥

我现在考虑的是第3部分,该任务完成了以下任务:ping非常大的URL列表(数量成千上万个),并检索与之关联的URL的x509证书。第1部分在这里(如何正确使用线程来ping URL),第2部分在这里(为什么我的连接池不实现我的线程代码)

自从我问了这两个问题以来,我现在得到了以下代码:

###### This is the code that pings a url and grabs its x509 cert #####

class SslClient
  attr_reader :url, :port, :timeout

  def initialize(url, port = '443')
    @url = url
    @port = port
  end

  def ping_for_certificate_info
    context = OpenSSL::SSL::SSLContext.new
    tcp_client = TCPSocket.new(url, port)
    ssl_client = OpenSSL::SSL::SSLSocket.new tcp_client, context
    ssl_client.hostname = url
    ssl_client.sync_close = true
    ssl_client.connect
    certificate = ssl_client.peer_cert
    verify_result = ssl_client.verify_result
    tcp_client.close
    {certificate: certificate, verify_result: verify_result }
  rescue => error
    {certificate: nil, verify_result: nil }
  end
end

上面的代码对我检索至关重要ssl_client.peer_cert下面,我有以下代码段,该段代码对证书的URL进行了多个HTTP ping:

  pool = Concurrent::CachedThreadPool.new
  pool.post do
    [LARGE LIST OF URLS TO PING].each do |struct|
       ssl_client = SslClient.new(struct.domain.gsub("*.", "www."), struct.scan_port)
       cert_info = ssl_client.ping_for_certificate_info
       struct.x509_cert = cert_info[:certificate]
       struct.verify_result = cert_info[:verify_result]
     end
   end

   pool.shutdown
   pool.wait_for_termination

   #Do some rails code with the database depending on the results.

到目前为止,当我运行此代码时,它的速度令人难以置信。我认为通过创建带有线程的线程池,代码会更快。情况似乎并非如此,我不确定为什么。大部分是因为我不知道线程,池,饥饿,锁等的细微差别。但是,在实现上述代码之后,我阅读了更多内容以尝试加快速度,然后再次感到困惑和可以对我如何使代码更快进行一些澄清。

对于初学者,请在此出色的文章中(ruby-concurrency-parallelism)我们得到以下定义和概念:

并发与并行性这些术语被松散使用,但是它们确实具有不同的含义。

并发:一次完成许多任务的艺术。通过在它们之间快速切换,用户似乎可以同时看到它们。并行性:实际上可以同时执行许多任务。它们不是同时出现,而是同时出现。并发最常用于IO繁重的应用程序。例如,Web应用程序可能会定期与数据库进行交互或发出大量网络请求。通过使用并发,即使我们等待数据库响应查询,我们也可以使应用程序保持响应。

这是可能的,因为Ruby VM允许其他线程在IO期间等待时运行。即使程序必须发出数十个请求,但如果我们使用并发性,则这些请求实际上将在同一时间发出。

另一方面,Ruby当前不支持并行。

因此,从本文的这篇文章中,我了解到我想要做的事情需要并发完成,因为我正在ping网络上的URL,并且Ruby当前不支持Parallelism。

接下来是让我感到困惑的地方。从我关于堆栈溢出的第1部分问题中,我在给我的评论中了解了以下内容,我应该执行以下操作:

使用线程池;不只是创建一千个并发线程。对于诸如连接到URL的等待很多的事情,您可以超额预订每个CPU内核的线程数,但不要过多。您必须进行试验。

另一个用户说:

您不会产生数千个线程,请使用连接池(例如https://github.com/mperham/connection_pool),这样您最多可以进行20-30个并发请求(此最大数目应通过测试网络的哪个点来确定性能下降,您会收到这些超时)

因此,对于这一部分,我转向concurrent-ruby并实现CachedThreadPoolFixedThreadPoolwith和a with10线程。我选择了一个“ CachedThreadPool”,因为在我看来,所需的线程数将由Threadpool负责。现在在并发红宝石的池文档中,我看到了:

pool = Concurrent::CachedThreadPool.new
pool.post do
  # some parallel work
end

我以为我们刚刚在第一篇文章中就确定了Ruby不支持并行性,那么线程池在做什么?它是同时工作还是并行工作?到底是怎么回事?是否需要线程池?同样在这个时候,我认为连接池和线程池只是可互换使用的相同。这两个池有什么区别,我需要哪一个?

在另一篇出色的文章《如何在Ruby和Rails中执行并发HTTP请求》中,本文介绍了Concurrent::Promises并发红宝石类形式,以避免锁并通过两个api调用确保线程安全。这是下面的代码片段,具有以下描述:

def get_all_conversations
  groups_thread = Thread.new do
    get_groups_list
  end

  channels_thread = Thread.new do
    get_channels_list
  end

  [groups_thread, channels_thread].map(&:value).flatten
end

每个请求都在其自己的线程上执行,该线程可以并行运行,因为它是阻塞的I / O。但是你能在这里看到收获吗?

在上面的代码中,我们刚刚提到的Ruby中还没有提到并行性。下面是方法Concurrent::Promise

def get_all_conversations
  groups_promise = Concurrent::Promise.execute do
    get_groups_list
  end

  channels_promise = Concurrent::Promise.execute do
    get_channels_list
  end

  [groups_promise, channels_promise].map(&:value!).flatten
end

因此,根据本文,这些请求是“并行”发出的。现在我们还在谈论并发吗?

最后,在这两篇文章中,他们讨论了Futures用于并发http请求。我不会详细介绍,但会在此处粘贴链接。

1.在Ruby on Rails应用程序中使用并发Ruby 2.通过在Ruby中实现Future学习并发性

同样,本文中所讨论的内容对我来说就像是Concurrent::Promise功能。我只想指出,这些示例说明了如何将这些概念用于需要结合在一起的两个不同的API调用。这不是我所需要的。我只需要快速执行数千个API调用并记录结果即可。

总之,我只想知道我该怎么做才能使我的代码更快并且线程安全以使其同时运行。我确实缺少使代码运行得更快的东西,因为现在它运行得太慢了,以至于我可能根本不使用线程。

概要

我必须使用线程ping数千个URL,以加快此过程。代码很慢,如果正确使用线程,线程池和并发,我会感到困惑。

弥敦奎师那

让我们看看您所描述的问题,并尝试一次解决这些问题:

您有两段代码,SslClient以及使用此ssl客户端的脚本。根据我对线程池的了解,您需要稍微改变使用线程池的方式。

从:

pool = Concurrent::CachedThreadPool.new
pool.post do
 [LARGE LIST OF URLS TO PING].each do |struct|
    ssl_client = SslClient.new(struct.domain.gsub("*.", "www."), struct.scan_port)
    cert_info = ssl_client.ping_for_certificate_info
    struct.x509_cert = cert_info[:certificate]
    struct.verify_result = cert_info[:verify_result]
  end
end

pool.shutdown
pool.wait_for_termination

至:

pool = Concurrent::FixedThreadPool.new(10) 

[LARGE LIST OF URLS TO PING].each do | struct |
  pool.post do 
   ssl_client = SslClient.new(struct.domain.gsub("*.", "www."), struct.scan_port)
   cert_info = ssl_client.ping_for_certificate_info
   struct.x509_cert = cert_info[:certificate]
   struct.verify_result = cert_info[:verify_result]
  end
end

pool.shutdown
pool.wait_form

在初始版本中,只有一个工作单元发布到池中。在第二个版本中,我们向池中发布的工作单元与中的项目一样多LARGE LIST OF URLS TO PING

为了进一步了解Ruby中的并发性与并行性,确实是由于GIL(全局解释器锁),Ruby不支持真正的并行性,但这仅在我们实际上在CPU上进行任何工作时才适用。在有网络请求的情况下,与IO绑定工作相比,CPU绑定工作持续时间可以忽略不计,这意味着您的用例非常适合使用线程。

同样,通过使用线程池,我们可以最大程度地减少CPU产生线程的开销。当我们使用线程池时,例如在Concurrent :: FixedThreadPool.new(10)的情况下,我们实际上是在限制池中可用的线程数,对于未绑定的线程池,每当一个单元时都会创建新线程存在工作,但池中的其余线程处于繁忙状态。

第一篇文章中,需要收集每个工作人员返回的结果,并且在发生异常的情况下(我是作者)采取有意义的行动。您应该能够使用该博客中提供的类,而无需进行任何更改。

让我们尝试使用Concurrent :: Future重写代码,因为在您的情况下,我们也需要结果。


thread_pool = Concurrent::FixedThreadPool.new(20)

executors = [LARGE LIST OF URLS TO PING].map do | struct |
  Concurrent::Future.execute({ executor: thread_pool }) do
    ssl_client = SslClient.new(struct.domain.gsub("*.", "www."), struct.scan_port)
    cert_info = ssl_client.ping_for_certificate_info
    struct.x509_cert = cert_info[:certificate]
    struct.verify_result = cert_info[:verify_result]
    struct
  end
end

executors.map(&:value)

我希望这有帮助。如有疑问,请在评论中提出,我将修改本意见以回答这些问题。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

使用JavaFX Tasks正确执行多线程和线程池

来自分类Dev

Ruby中的线程会提高并发性吗?

来自分类Dev

多线程检查映射大小和并发性

来自分类Dev

'并发性'与'并行性'-'线程'与'流程'

来自分类Dev

'并发性'与'并行性'-'线程'与'流程'

来自分类Dev

如何使这个简单的Groovy代码并发/多线程?

来自分类Dev

Java - 使用线程池并发发送多个文件时出错

来自分类Dev

使用Boost线程和io_service创建线程池

来自分类Dev

Erlang如何在不使用OS线程的情况下实现并发性?

来自分类Dev

即使使用了线程池,Android Cordova插件也会锁定UI线程

来自分类Dev

异步使用线程池?

来自分类Dev

检查线程是否返回线程池

来自分类Dev

JavaFX并发性:多个线程修改单个StringProperty

来自分类Dev

生产者/消费者线程并发性Java

来自分类Dev

为什么这个Ruby线程代码输出2?

来自分类Dev

线程,线程池和Unity的ThreadScope

来自分类Dev

线程池和对象的线程问题

来自分类Dev

线程池和Parallel.For组合-是否可能

来自分类Dev

Java-何时使用固定大小的线程池和可变大小的线程池?

来自分类Dev

Java-何时使用固定大小的线程池和可变大小的线程池?

来自分类Dev

Swift是否有任何本机并发和多线程支持?

来自分类Dev

播放框架和线程池

来自分类Dev

线程池未按预期使用线程?

来自分类Dev

当一个线程初始化该值而其他线程仅读取该值时,是否存在并发性问题

来自分类Dev

Servlet中的线程和并发

来自分类Dev

进程,线程和并发编程

来自分类Dev

我对ScheduledThreadPoolExecutor.scheduleAtFixedRate和可能的并发感到困惑。为什么会有线程池?

来自分类Dev

我对ScheduledThreadPoolExecutor.scheduleAtFixedRate和可能的并发感到困惑。为什么会有线程池?

来自分类Dev

使用 Executor Framrwork 的线程池

Related 相关文章

  1. 1

    使用JavaFX Tasks正确执行多线程和线程池

  2. 2

    Ruby中的线程会提高并发性吗?

  3. 3

    多线程检查映射大小和并发性

  4. 4

    '并发性'与'并行性'-'线程'与'流程'

  5. 5

    '并发性'与'并行性'-'线程'与'流程'

  6. 6

    如何使这个简单的Groovy代码并发/多线程?

  7. 7

    Java - 使用线程池并发发送多个文件时出错

  8. 8

    使用Boost线程和io_service创建线程池

  9. 9

    Erlang如何在不使用OS线程的情况下实现并发性?

  10. 10

    即使使用了线程池,Android Cordova插件也会锁定UI线程

  11. 11

    异步使用线程池?

  12. 12

    检查线程是否返回线程池

  13. 13

    JavaFX并发性:多个线程修改单个StringProperty

  14. 14

    生产者/消费者线程并发性Java

  15. 15

    为什么这个Ruby线程代码输出2?

  16. 16

    线程,线程池和Unity的ThreadScope

  17. 17

    线程池和对象的线程问题

  18. 18

    线程池和Parallel.For组合-是否可能

  19. 19

    Java-何时使用固定大小的线程池和可变大小的线程池?

  20. 20

    Java-何时使用固定大小的线程池和可变大小的线程池?

  21. 21

    Swift是否有任何本机并发和多线程支持?

  22. 22

    播放框架和线程池

  23. 23

    线程池未按预期使用线程?

  24. 24

    当一个线程初始化该值而其他线程仅读取该值时,是否存在并发性问题

  25. 25

    Servlet中的线程和并发

  26. 26

    进程,线程和并发编程

  27. 27

    我对ScheduledThreadPoolExecutor.scheduleAtFixedRate和可能的并发感到困惑。为什么会有线程池?

  28. 28

    我对ScheduledThreadPoolExecutor.scheduleAtFixedRate和可能的并发感到困惑。为什么会有线程池?

  29. 29

    使用 Executor Framrwork 的线程池

热门标签

归档