我现在考虑的是第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
并实现CachedThreadPool
了FixedThreadPool
with和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] 删除。
我来说两句