On 6/16/07, Trans <transfire / gmail.com> wrote:
> Thanks Erwin, that helps. I was toying with not having any methd
> arguments. It's interesting, in that it can be done. However, in a
> case like the above it requires intantiaing a new object for dealing
> with type.
>
>   class X
>     ...
>     def go
>       @ary.each { |x|
>         TypeRunner.new(x).run
>       }
>     end
>   end
>
>    class TypeRunner
>      ...
>    end
>
>    X.new(['a', 'b', 'c']).go
>
> Am I right about this being thread safe(r) where the first version was
> not?

Yes, this is thread safe... each thread that calls #go gets its own
set of objects (TypeRunners) that are separate from the objects in the
other threads.

> Also, I was thinking. If it's thread safe to just pass type as an
> argument, one would think there could be a way to define a "locked"
> instance var that is essentially the same thing but without all the
> overhead.

It seems like it should be simple, but there's a lot of complexity
when programming multithreaded code. In your example you're not
changing the state of anything, so that's why you can simplify
things... but things get out of hand fast. See my comments past the
end of the code below.

Here's my attempt at a very *simple* way to do what you're talking
about... I had trouble getting the methods put in the right namespace,
so maybe my module code isn't the most elegant but it does what I want
it to do.

require 'thread'

module Locker
  def self.included mod
    (class << mod; self; end).instance_eval do

      define_method(:attr_locked) do |*names|
        class_eval do
          @@locks ||= {}

          names.each do |name|
            # make a lock for each attribute
            @@locks[name] = Mutex.new

            # getter
            define_method(name) do
              @@locks[name].synchronize do
                instance_variable_get("@#{name}")
              end
            end

            # setter
            define_method("#{name}=") do |value|
              @@locks[name].synchronize do
                instance_variable_set("@#{name}", value)
                sleep 5
              end
            end

          end

        end # class_eval
      end # attr_locked

    end # instance_eval
  end
end

class DooWop
  include Locker
  attr_locked :chord, :name

  def initialize name, chord
    @name, @chord = name, chord
  end
end

x = DooWop.new('The Platters', 'Fmaj6/9')

$stdout.sync = true
def tprint string
  print "#{Thread.current} #{string}\n"
end

Thread.new{ tprint "1. #{x.name}" }

# this will block access to x.name for 5 seconds
Thread.new{ tprint "2. setting name"; x.name = 'The Orioles' }

# this will wait for the above thread to finish
Thread.new{ sleep 1; tprint "3. #{x.name}" }

# this isn't blocked
Thread.new{ tprint "4. #{x.chord}" }

# this will block access to x.chord for 5 seconds
Thread.new do
  tprint "5. setting chord"
  x.chord = 'Dm7b5'
  tprint "5. #{x.chord}"
end

# this could be indeterminite, we didn't wait for the writer to finish
Thread.new{ tprint "6. unsafe: #{x.instance_eval{@name}}" }

sleep 0.5 until Thread.list.size == 1 # should be joining here instead

This is helpful only in the simplest cases. First it blocks everyone
while in the getter or setter... ideally we shouldn't make the getters
wait for the other getters to finish. Next, if you try to read the
variable more than once in a method, you can still end up with
different values.

You'd need to wrap the section of code that involves the var in a
#synchronize block... but wrap the *least* amount of code necessary
because you want to hurry up and let the other threads do their work.
There's no way to "automate" that. The code above is only useful in
the most simple applications.

Further, we've only been talking about one variable at a time.  Often
you'll need to lock several variables, like maybe we are going to look
up on Google which songs by @name have the chord @chord.  We'll need
to get those values at the *same time* because if we read chord,
(another thread interrupts here and changes @name), then read name...
that's a problem, and our "locked" variables can't do anything to
help.

There was a thread on this list a few days ago with a title like
"synchronized attr", I only skimmed it but I think they were talking
about why you can't really boil thread synchronization down to a
one-size-fits-all solution. There are a few data structures (Monitor,
ConditionVariable, Queue, etc) that really help out, but there's no
way to just call some method to magically make your code thread
safe... yet?

Regards,
Erwin