Trans
6/17/2007 12:27:00 PM
On Jun 16, 3:52 am, "Erwin Abbott" <erwin.abb...@gmail.com> wrote:
> On 6/16/07, Trans <transf...@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?
I was thinking more along the lines of "transparent arguments". Here's
the idea in pseudo-code. I'll represent these "transparent arguments"
via an underscore.
class Q
def f
_t = "world"
g("hello")
end
def g(x)
puts x + ' ' + _t
h("goodbye")
end
def h(x)
puts x + ' ' + _t
end
end
Q.new.f
produces
hello world
goodbye world
In effect, _t is being tacked on to the arguments of every call, and
is thus getting passed around just like any other argument, but
without need to explicitly "slot" for it in the method interface. In
effect, transparent arguments are a sort of symbiosis of instance var
and argument. Now, I know that some will see this and think it a
violation of the very purpose of functions, ie. the isolation of name
spaces. However, one should see these as a form of instance var, not
argument --they differ only in that they piggy back on the argument
passing mechanism.
T.