From: Tim Bates Date: 2004-02-08T14:34:48+09:00 Subject: Multi-threading lesson wanted Hi all, I've not done a lot of multi-threaded programming before, and I now find myself in the middle of writing a multi-threaded application. I'd appreciate someone who knows more about thread-safety than me to do a review of the code below and tell me where it falls short in that respect (as I'm sure it does). I can't immediately see anywhere in the code which is likely to cause problems and I've tried to write it so that it doesn't have problems, but I haven't used Mutex or any other library traditionally associated with thread-safety so I'm feeling a bit nervous about it. I learn best by getting my hands dirty, so I've given it a shot and I'd like someone to point out my mistakes. Some background: the application (known as SAMS) is essentially no more than a database-backed DRb server. DRb takes care of most of the threading, by starting a new thread for each connection for me. Rather than starting a separate database connection for each thread, or trying to share one connection between all threads, I've tried to write a connection pooling class. The other application code generally requests a connection from the pool immediately before performing its queries and releases it immediately after, so unless the queries go for quite some time no one thread will be hanging onto a connection for more than a second or so. Below is the code I have written. The idea is that other application code calls SAMS::Database.get_handle{ |handle| handle.execute... } and this module takes care of the rest. Also, the application's shutdown code will call SAMS::Database.destroy to cleanly disconnect all the handles. require 'dbi' module SAMS module Database # These values will eventually be taken from a configuration file MAX_FREE_HANDLES = 10 MAX_HANDLES = 20 @handles = [] @free_handles = [] @stopped = false class << self def new_handle # The DB connection parameters will eventually be read from a # configuration file somewhere h = DBI.connect('DBI:pg:sams', 'samsd', 'foo') h['AutoCommit'] = false @handles << h h end def destroy_handle h = @free_handles.shift if h h.disconnect @handles.delete h end end def allocate_handle if @free_handles.empty? if @handles.length < MAX_HANDLES return new_handle else while @free_handles.empty? sleep(1) end return allocate_handle end else return @free_handles.shift end end private :new_handle, :destroy_handle, :allocate_handle def get_handle return nil if @stopped begin h = allocate_handle yield h ensure # Make sure the handle gets put back in the list @free_handles << h end while @free_handles.length > MAX_FREE_HANDLES destroy_handle end end def destroy # Don't allocate any more handles @stopped = true # Destroy all handles, waiting for them to be released first while @handles.length > 0 if @free_handles.length > 0 destroy_handle else # Wait for a handle to be released sleep(1) end end end end end end -- Tim Bates tim@bates.id.au