Professional Web Applications Themes

Multi-threading lesson wanted - Ruby

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 ...

  1. #1

    Default 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
    id.au


    Tim Guest

  2. #2

    Default Re: Multi-threading lesson wanted

    Tim Bates wrote: 
    ...

    You have some excitement ahead of you!

    Here's one potential problem:
     
    ... 

    At this point we've just decided that free_handles is not empty.
     

    But before the next line happens, another thread executes and steals the
    last handle. The return value is nil.

    In the empty case, there is a less serious problem:
     

    Two threads can get to this point at the same time. Then they both
    create a new handle, even though that might result in MAX_HANDLES+1
    handles. So, _very_ gradually, the size of the pool grows.
     

    By the same token, this code:
     

    could cause too many handles to be deleted: it's possible for N threads
    to be scheduled to check the condition, and then after all that checking
    is done, each calls destroy_handle. So you end up with too few free
    handles left.

    There is also a performance problem with code like:
     

    Rather than wake up each second and check for an available handle, the
    thread would be better off going to sleep indefinitely, and being
    wakened when a handle is available. This would save context-switches at
    a rate of twice per waiting thread per second. And it would reduce delay
    from 0.5sec average to (probably) milliseconds.

    On the positive side, this code won't ever give the same handle to two
    threads, because #shift is atomic, as far as ruby threads are concerned.

    The construct that would probably apply best here is the Queue in
    thread.rb. It would replace free_handles (just create 20 handles and
    put them on the queue, use #pop and #push to wait for and to release
    handles). It would solve the performance problem as described (a thread
    goes to sleep waiting for a handle, and is woken by another thread when
    there is a handle available in the queue). It would also avoid the race
    condition inherent in checking empty? and then shifting.

    However, it won't help with the MAX_FREE_HANDLES logic that you've
    designed. You would probably have to use Thread.critical for that, so
    that you can check the size of the queue and be certain that it isn't
    changing while you decide to destroy a handle or not.


    Joel Guest

  3. #3

    Default Re: Multi-threading lesson wanted


    "Tim Bates" <id.au> schrieb im Newsbeitrag
    news:id.au...
    <snip>
     
    handles.

    You definitely need some kind of synchronization mechanism that makes
    accesses to the shared connection pool thread safe. In your case a
    ConditionVariable will help you with the max connection logic.

    See section "Logging from multiple threads" for example usage of a
    ConditionVariable at
    http://www.rubygarden.org/ruby?MultiThreading

    I'd implement a Semaphore using a ConditionVariable and a Mutex; that way
    you can initialize the semaphore with the max connection value and count
    the semaphore down when you take a connection from the pool and increment
    it when you put it back. If the semaphore is zero, the next thread trying
    to decrease the semaphore is put to sleep and will wake up when another
    thread increments the semaphore.

    You can as well use the semaphore implementation in the RAA:
    http://raa.ruby-lang.org/list.rhtml?name=semaphore

    <snip>

    As an additional note: I would not use module methods for the pool
    handling. Instead I'd instantiate an instance of the pool. This is IMHO
    better since the pool is not necessarily a singleton: Just think of an
    application that needs to access more than one database and hence use more
    than one connection configuration.

    Kind regards

    robert

    Robert Guest

  4. #4

    Default Re: Multi-threading lesson wanted

    Robert Klemme wrote:
     
    >
    > handles.
    >
    > You definitely need some kind of synchronization mechanism that makes
    > accesses to the shared connection pool thread safe. In your case a
    > ConditionVariable will help you with the max connection logic.
    >
    > See section "Logging from multiple threads" for example usage of a
    > ConditionVariable at
    > http://www.rubygarden.org/ruby?MultiThreading
    >
    > I'd implement a Semaphore using a ConditionVariable and a Mutex; that way
    > you can initialize the semaphore with the max connection value and count
    > the semaphore down when you take a connection from the pool and increment
    > it when you put it back. If the semaphore is zero, the next thread trying
    > to decrease the semaphore is put to sleep and will wake up when another
    > thread increments the semaphore.
    >
    > You can as well use the semaphore implementation in the RAA:
    > http://raa.ruby-lang.org/list.rhtml?name=semaphore
    >
    > <snip>
    >
    > As an additional note: I would not use module methods for the pool
    > handling. Instead I'd instantiate an instance of the pool. This is IMHO
    > better since the pool is not necessarily a singleton: Just think of an
    > application that needs to access more than one database and hence use more
    > than one connection configuration.
    >
    > Kind regards
    >
    > robert
    >[/ref]
    Why doesn't the counting semaphore library come standard with Ruby? I
    haven't done anything multithreaded in a little while, but I remember
    being annoyed I didn't have a counting semaphore. Is there a particular
    reason it's not part of the standard library? Obviously not everything
    should come standard but this seems to be the sort of thing that should.

    Charles Comstock
    Charles Guest

  5. #5

    Default Re: Multi-threading lesson wanted


    "Charles Comstock" <wustl.edu> schrieb im Newsbeitrag
    news:c07jo8$l5f$wustl.edu... 
    > >
    > > handles.
    > >
    > > You definitely need some kind of synchronization mechanism that makes
    > > accesses to the shared connection pool thread safe. In your case a
    > > ConditionVariable will help you with the max connection logic.
    > >
    > > See section "Logging from multiple threads" for example usage of a
    > > ConditionVariable at
    > > http://www.rubygarden.org/ruby?MultiThreading
    > >
    > > I'd implement a Semaphore using a ConditionVariable and a Mutex; that[/ref][/ref]
    way [/ref]
    count [/ref]
    increment [/ref]
    trying [/ref]
    another [/ref]
    IMHO [/ref]
    more 
    > Why doesn't the counting semaphore library come standard with Ruby?[/ref]

    Dunno.
     

    Yeah, that's true. +1 for including Semaphore in "Thread". Matz?

    Regards

    robert

    Robert Guest

Similar Threads

  1. Unwanted multi-threading with CFLOCK
    By ColdFusionSmurf in forum Coldfusion Server Administration
    Replies: 0
    Last Post: January 9th, 03:06 PM
  2. 108528-16 v 108528-20 (cpio multi-threading)?
    By Jonno in forum Sun Solaris
    Replies: 6
    Last Post: August 28th, 12:36 PM
  3. Replies: 5
    Last Post: August 21st, 04:12 AM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139