即使只有一个线程在线程池中,并发会发生并发吗?

我正在使用Rails 5和Ruby 2.4。 我怎么能弄明白,或者你能看出下面的内容,是否有多个线程在同一时间运行?

pool = Concurrent::FixedThreadPool.new(1) promises = links.map do |link| Concurrent::Promise.execute(executor: pool) do result = process_link(link) if result if result.kind_of?(Array) result.each do |my_obj| my_obj.update_attributes({ :a => a }) records_processed = records_processed + my_obj.matches.count end else records_processed = records_processed + result.matches.count result.update_attributes({ :a => a }) end end end end promises.map(&:wait).map(&:value!) 

由于我已将我的池设置为“1”,我的假设是没有任何内容同时运行,但我一直收到此错误…

 Error during processing: (ActiveRecord::ConnectionTimeoutError) could not obtain a connection from the pool within 5.000 seconds (waited 5.002 seconds); all pooled connections were in use /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:202:in `block in wait_poll' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:193:in `loop' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:193:in `wait_poll' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:154:in `internal_poll' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:278:in `internal_poll' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:148:in `block in poll' /Users/nataliab/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:158:in `synchronize' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:148:in `poll' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:717:in `acquire_connection' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:490:in `checkout' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:364:in `connection' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:883:in `retrieve_connection' /Users/nataliab/.rvm/gems/ruby-2.4.0@global/gems/activerecord-5.0.1/lib/active_record/connection_handling.rb:128:in `retrieve_connection' 

我没有得到上面的错误,如果我修改我的代码运行我肯定没有并发进行…

 links.each do |link| result = process_link(link) if result if result.kind_of?(Array) result.each do |race| my_obj.update_attributes({ :a => a }) records_processed = records_processed + my_obj.matches.count end else records_processed = records_processed + result.matches.count result.update_attributes({ :a => a }) end end end 

编辑:这是我的开发环境的数据库配置。 另请注意,所有这些都在rails控制台中运行。

 development: adapter: postgresql encoding: utf8 database: sims username: postgres password: password pool: 5 timeout: 15000 host: 127.0.0.1 

您假设多个线程必须仅因为连接池耗尽而并发运行是不正确的。 仅仅因为连接仍然从连接池“检出”并不意味着当前正在线程中的签出连接上执行查询,这只是意味着线程的连接尚未被检入。线程可以处于空闲状态,但只要尚未明确终止,它仍然可以保持连接池的连接。

由于ActiveRecord Connections是线程本地的,因此您可以通过在多个线程上运行ActiveRecord查询来耗尽连接池,就像在这种情况下一样。 (每次调用Concurrent::FixedThreadPool.new(1) ,都会创建一个新线程。)即使您一次只在一个线程上运行查询,默认情况下,每个线程上的连接仍然保持打开状态直到他们被终止

为避免这种情况,您可以在使用它们之后手动签入连接,或确保您的线程被终止(终止),以便池可以恢复(收集)它们的连接。

  • 要手动签入连接,请参阅ConnectionPool文档以获取选项。 最简单的方法是将ActiveRecord代码包装在with_connection块中:

     Concurrent::Promise.execute(executor: pool) do ActiveRecord::Base.connection_pool.with_connection do # update_attributes, etc end end 
  • 要确保所有线程都已终止,请在完成使用后在线程池上调用#shutdown然后调用#shutdown

     values = promises.map(&:value!) pool.shutdown pool.wait_for_termination 

您假设只有一个线程不正确。 有两个 – 线程池中的一个和在线程池中生成一个的主要一个。

当您使主线程等待并且它不应该访问数据库时,您可能会感到困惑。 这并不意味着它仍然没有连接,因此阻止其他线程获取连接。

根据经验,您的数据库连接池应至少设置为生成的线程数 + 1。在这种情况下 – 2。


代码轻松重现:

 # migration class CreateFoos < ActiveRecord::Migration[5.0] def change create_table :foos do |t| t.integer :bar end end end # model class Foo < ApplicationRecord end # rake task task experiment: :environment do Foo.create pool = Concurrent::FixedThreadPool.new(1) promise = Concurrent::Promise.execute(executor: pool) do Foo.first.update_attributes!(bar: rand(-42..42)) end promise.wait.value! end 

config/database.yml中将pool设置为1并运行该任务。 你会收到一个错误。 将它设置为2 - 它会很好。

您可以增加池中的线程数,并添加至少那么多的处理承诺。 对于数据库连接池,您将始终失败= 线程池中的线程数,如果在config/database.yml再添加一个,则会成功。

由于您将固定线程池定义为具有一个线程,因此我假设您没有实现任何类型的并发。 查看您的错误,似乎池中的一个可用线程占用太长时间并导致连接超时exception。

当您更改代码实现以使其不包含线程池时,应用程序显式为单线程,而不会因为等待池中的线程而导致连接超时。 尝试增加线程池的大小(可能增加到3或5)并查看是否仍然获得相同的exception。