Does concurrency happen even when only one thread is in a thread pool?

前端 未结 3 1130
失恋的感觉
失恋的感觉 2021-02-05 10:46

I\'m using Rails 5 and Ruby 2.4. How can I figure out, or can you tell by looking at the below, whether there are multiple threads running at the same time?

poo         


        
相关标签:
3条回答
  • 2021-02-05 11:25

    Your assumption that multiple threads must be running concurrently just because the connection pool is being exhausted is not correct. Just because a connection is still 'checked out' from the connection pool doesn't mean that a query is currently being executed on the checked-out connection in a thread, it simply means that the thread's connection hasn't been checked back in. The thread could be sitting idle but still holding onto a connection from the connection pool as long as it hasn't been explicitly terminated.

    Since ActiveRecord Connections are thread-local, you can exhaust the connection pool by running ActiveRecord queries on multiple threads, as you are doing in this case. (Every time Concurrent::FixedThreadPool.new(1) is called, a new thread is created.) Even if you're only running queries on a single thread at a time, by default a connection will still be held open on every thread until they are terminated.

    To avoid this, you can either manually check in connections after using them, or ensure your threads are terminated (killed) so that their connections can be recovered (reaped) by the pool.

    • To manually check in connections, refer to the ConnectionPool documentation for your options. The easiest way is to wrap your ActiveRecord code in a with_connection block:

      Concurrent::Promise.execute(executor: pool) do
        ActiveRecord::Base.connection_pool.with_connection do
          # update_attributes, etc
        end
      end
      
    • To ensure all threads are terminated, call #shutdown followed by #wait_for_termination on the thread pool after you're finished using it:

      values = promises.map(&:value!)
      pool.shutdown
      pool.wait_for_termination
      
    0 讨论(0)
  • 2021-02-05 11:35

    You assumption that there is only one thread is incorrect. There are two - the one in the thread pool and the main one that spawned the one in the thread pool.

    You might be confused as you made the main thread wait and it shouldn't access the database. That doesn't mean that it still doesn't hold a connection, hence preventing the other thread from acquiring one.

    As a rule of thumb your database connection pool should be set to at least number of threads spawned + 1. In this case - 2.


    Code to easily reproduce:

    # 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
    

    Set pool to 1 in your config/database.yml and run the task. You will get an error. Set it to 2 - it will be just fine.

    You can increase the number of threads in the pool and add at least that much promises for processing. You will consistently fail for database connection pool = number of threads in the thread pool and succeed if you add one more in config/database.yml.

    0 讨论(0)
  • 2021-02-05 11:40

    Since you are defining the fixed thread pool to have one thread, I would assume that you are not achieving any kind of concurrency. Looking at your error it appears that the one available thread from the pool was busy for too long and caused the connection timeout exception.

    When you altered your code implementation so that it did not contain a thread pool, the application was explicitly single threaded without the possibility of a connection timeout due to waiting on threads from a pool. Try increasing the size of your thread pool (perhaps to 3 or 5) and see if you are still getting that same exception.

    0 讨论(0)
提交回复
热议问题