[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Reasonable practice?

Trans

6/14/2007 11:49:00 PM

It the following reasonable? How thread safe is it?

class X
attr :type

def initialize(ary)
@ary = ary
end

def go
@ary.each { |x|
@type = x
run
}
end

def run
puts type
end

end

X.new(['a', 'b', 'c']).go

Is changing the instance variable through each iteration a bad idea?
Of course I could pass the type to #run as an argument, but I'm
experimenting with the concept of "service points" and was wondering
if type could safely be one in this case. My alternative to keep type
as a serve point is to create a Runner class that took type as its
initialize argument, but that seems code heavy.

Thanks,
T.


14 Answers

Aatch

6/15/2007 4:08:00 AM

0

Trans wrote:
> It the following reasonable? How thread safe is it?
>
> class X
> attr :type
>
> def initialize(ary)
> @ary = ary
> end
>
> def go
> @ary.each { |x|
> @type = x
> run
> }
> end
>
> def run
> puts type
> end
>
> end
>
> X.new(['a', 'b', 'c']).go
>
> Is changing the instance variable through each iteration a bad idea?
> Of course I could pass the type to #run as an argument, but I'm
> experimenting with the concept of "service points" and was wondering
> if type could safely be one in this case. My alternative to keep type
> as a serve point is to create a Runner class that took type as its
> initialize argument, but that seems code heavy.
>
> Thanks,
> T.

If you just want to print out all of the members of an array, it looks
fine to me. Although maybe you could just substitute the 'run' for 'puts
x'. It means about 4 less lines and one or two less variables. Unless
you plan to use that 'type' variable another time. But thats just me, i
dont always like creating one-use methods, especially such small ones.

--
Posted via http://www.ruby-....

Trans

6/15/2007 6:22:00 AM

0



On Jun 15, 12:07 am, Aatch Random <blad...@gmail.com> wrote:
> If you just want to print out all of the members of an array, it looks
> fine to me. Although maybe you could just substitute the 'run' for 'puts
> x'. It means about 4 less lines and one or two less variables. Unless
> you plan to use that 'type' variable another time. But thats just me, i
> dont always like creating one-use methods, especially such small ones.

My example is a drastic simplification of what I'm actually doing. My
concern is just with the practice of repeatedly changing an instance
var. I've just never used an instance var in this way before, and
worry I might be over looking something --especially thread issues.

I used to write gigantic methods. Over the years I have learned that
smaller is better. I'm getting to the point now that a method with
more than nine lines is rare. You might want to give it some further
consideration.

Thanks,
T.


Robert Dober

6/15/2007 8:22:00 AM

0

On 6/15/07, Trans <transfire@gmail.com> wrote:
> It the following reasonable? How thread safe is it?
>
> class X
> attr :type
>
> def initialize(ary)
> @ary = ary
> end
>
> def go
> @ary.each { |x|
> @type = x
-------------------------> Problem, Thread 1 gets interrupted by Thread2
-------------------------> Thread2 changes @type
> run
> }
> end
>
> def run
> puts type
> end
>
> end
>
> X.new(['a', 'b', 'c']).go
>

Tom I do not know how big the chance is that the scenario above will
happen but it is *not* thread safe.

I just recently was awoken by Robert Klemme about instance variable issues,
so I am not an expert.
Could you come up with passing local variables around as params. The
method calls might become ugly :( or your methods longer :((.

In the simplfied code it will work out nicely but how will it scale to
your real application.

I am working on closure properties right now -- they will not solve
your problem yet, but I was thinking a lot about lockable properties,
this will look like this

require 'labrador/properties'
class X
extend Properties
property :type, :lockable

def go ...

lock_property :type, :lock_type => :write_lock
self.type = x
run
ensure
unlock_property :type

but my mileage on this will be bad (1) as I am not a Thread guru and I
do not want to reinvent the wheel, but I am thinking about the
BusyFlag class from Java Threads.

BTW if you look for performance just forget it, I expect a performance
penalty of factor 5 :( for closure based properties.

Cheers
Robert

(1) and non lockable properties are not ready yet :(



--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Erwin Abbott

6/16/2007 3:02:00 AM

0

On 6/15/07, Robert Dober <robert.dober@gmail.com> wrote:
> On 6/15/07, Trans <transfire@gmail.com> wrote:
> > It the following reasonable? How thread safe is it?
> > ...
> > def go
> > @ary.each { |x|
> > @type = x
> -------------------------> Problem, Thread 1 gets interrupted by Thread2
> -------------------------> Thread2 changes @type
> > run
> > }
> > end
> >
> > def run
> > # maybe some work is being done here too?
---------------------------> Threads can also interrupt here and change @type
> > puts type
> > end
> >

Robert's right, and I added another place threads might step on
eachother. Since this is a simplification, if you're calling #type
more than once in the #run method, you might even get different values
within the #run method! Like this:

def run
puts type # => String
--------> another thread calls #go or does something otherwise to change @type
puts type # => might be a different value now!
puts type # => might be the 3rd different value returned by #type
end

I don't know anything about lockable properties (haven't heard of
them), but I think the best way to make your code thread safe is to
only using locally scoped variables. Constants, instance, class,
global, etc variables will probably all require some sort of mechanism
to control access (mutex, semaphore, condition var, etc) if they're
values are being changed. Those mechanisms typically erode the
performance advantage of parallelizing your program because they spend
time checking if it's safe to change some variable, and spend time
waiting for a lock to open and whatnot. But when you need to use them
they are handy tools.

You'll probably have to deal with writing instance variables
somewhere, like #initialize but that doesn't typically cause thread
safety issues because it's only ever called once on an object. If you
only ever read those instance variables, you'll be mostly in the
clear. When you start changing the state of the object you have to be
careful.

In your example, you could remove the line @type = x; and change the
next line to: run(x). Then redefine #run accordingly. You'll notice
your example code doesn't really need to change the object's state to
get the job done, so just write it in a way that doesn't do that and
you'll be thread safe. You may or may not be able to apply that idea
to your actual non-example code though.

Regards,
Erwin

Trans

6/16/2007 4:51:00 AM

0



On Jun 15, 11:02 pm, "Erwin Abbott" <erwin.abb...@gmail.com> wrote:
> On 6/15/07, Robert Dober <robert.do...@gmail.com> wrote:> On 6/15/07, Trans <transf...@gmail.com> wrote:
> > > It the following reasonable? How thread safe is it?
> > > ...
> > > def go
> > > @ary.each { |x|
> > > @type = x
> > -------------------------> Problem, Thread 1 gets interrupted by Thread2
> > -------------------------> Thread2 changes @type
> > > run
> > > }
> > > end
>
> > > def run
> > > # maybe some work is being done here too?
>
> ---------------------------> Threads can also interrupt here and change @type
>
> > > puts type
> > > end
>
> Robert's right, and I added another place threads might step on
> eachother. Since this is a simplification, if you're calling #type
> more than once in the #run method, you might even get different values
> within the #run method! Like this:
>
> def run
> puts type # => String
> --------> another thread calls #go or does something otherwise to change @type
> puts type # => might be a different value now!
> puts type # => might be the 3rd different value returned by #type
> end
>
> I don't know anything about lockable properties (haven't heard of
> them), but I think the best way to make your code thread safe is to
> only using locally scoped variables. Constants, instance, class,
> global, etc variables will probably all require some sort of mechanism
> to control access (mutex, semaphore, condition var, etc) if they're
> values are being changed. Those mechanisms typically erode the
> performance advantage of parallelizing your program because they spend
> time checking if it's safe to change some variable, and spend time
> waiting for a lock to open and whatnot. But when you need to use them
> they are handy tools.
>
> You'll probably have to deal with writing instance variables
> somewhere, like #initialize but that doesn't typically cause thread
> safety issues because it's only ever called once on an object. If you
> only ever read those instance variables, you'll be mostly in the
> clear. When you start changing the state of the object you have to be
> careful.
>
> In your example, you could remove the line @type = x; and change the
> next line to: run(x). Then redefine #run accordingly. You'll notice
> your example code doesn't really need to change the object's state to
> get the job done, so just write it in a way that doesn't do that and
> you'll be thread safe. You may or may not be able to apply that idea
> to your actual non-example code though.

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
attr :type

def initialize(ary)
@ary = ary
end


def go
@ary.each { |x|
TypeRunner.new(x).run
}
end
end

class TypeRunner
attr :type
def initialize(type)
@type = type
end

def run
puts type
end
end

X.new(['a', 'b', 'c']).go

Am I right about this being thread safe(r) where the first version was
not?

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.

T.

> Regards,
> Erwin


Erwin Abbott

6/16/2007 7:53:00 AM

0

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

Erwin Abbott

6/16/2007 8:01:00 AM

0

I made a big mistake in that code. Instead of each instance of DooWop
having its own set of locks, every instance would share one set.
Calling a setter in one DooWop object could block a getter/setter in a
totally separate DooWop object. But I think you get the idea, and
maybe agree it's not really worth the (small) effort to fix it.

Regards,
Erwin

Robert Dober

6/16/2007 8:42:00 AM

0

> On 6/16/07, Trans <transfire@gmail.com> wrote:

Robert Klemme

6/17/2007 10:49:00 AM

0

On 16.06.2007 06:50, Trans wrote:
>
> On Jun 15, 11:02 pm, "Erwin Abbott" <erwin.abb...@gmail.com> wrote:
>> On 6/15/07, Robert Dober <robert.do...@gmail.com> wrote:> On 6/15/07, Trans <transf...@gmail.com> wrote:
>>>> It the following reasonable? How thread safe is it?
>>>> ...
>>>> def go
>>>> @ary.each { |x|
>>>> @type = x
>>> -------------------------> Problem, Thread 1 gets interrupted by Thread2
>>> -------------------------> Thread2 changes @type
>>>> run
>>>> }
>>>> end
>>>> def run
>>>> # maybe some work is being done here too?
>> ---------------------------> Threads can also interrupt here and change @type
>>
>>>> puts type
>>>> end
>> Robert's right, and I added another place threads might step on
>> eachother. Since this is a simplification, if you're calling #type
>> more than once in the #run method, you might even get different values
>> within the #run method! Like this:
>>
>> def run
>> puts type # => String
>> --------> another thread calls #go or does something otherwise to change @type
>> puts type # => might be a different value now!
>> puts type # => might be the 3rd different value returned by #type
>> end
>>
>> I don't know anything about lockable properties (haven't heard of
>> them), but I think the best way to make your code thread safe is to
>> only using locally scoped variables. Constants, instance, class,
>> global, etc variables will probably all require some sort of mechanism
>> to control access (mutex, semaphore, condition var, etc) if they're
>> values are being changed. Those mechanisms typically erode the
>> performance advantage of parallelizing your program because they spend
>> time checking if it's safe to change some variable, and spend time
>> waiting for a lock to open and whatnot. But when you need to use them
>> they are handy tools.
>>
>> You'll probably have to deal with writing instance variables
>> somewhere, like #initialize but that doesn't typically cause thread
>> safety issues because it's only ever called once on an object. If you
>> only ever read those instance variables, you'll be mostly in the
>> clear. When you start changing the state of the object you have to be
>> careful.
>>
>> In your example, you could remove the line @type = x; and change the
>> next line to: run(x). Then redefine #run accordingly. You'll notice
>> your example code doesn't really need to change the object's state to
>> get the job done, so just write it in a way that doesn't do that and
>> you'll be thread safe. You may or may not be able to apply that idea
>> to your actual non-example code though.
>
> 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
> attr :type
>
> def initialize(ary)
> @ary = ary
> end
>
>
> def go
> @ary.each { |x|
> TypeRunner.new(x).run
> }
> end
> end
>
> class TypeRunner
> attr :type
> def initialize(type)
> @type = type
> end
>
> def run
> puts type
> end
> end

You can do it differently by using command pattern:

X = Struct.new :ary do
def go
TypeRunner.new(ary).go
end
end

TypeRunner = Struct.new :type do
def go
type.each {|x| self.type = x; run}
end

def run
end
end

or

X = Struct.new :ary do
def go
tr = TypeRunner.new(ary)
ary.each {|x| tr.type = x; tr.run}
end
end

TypeRunner = Struct.new :type do
def run
end
end

> X.new(['a', 'b', 'c']).go
>
> Am I right about this being thread safe(r) where the first version was
> not?

Yes, the concept is known as "thread confinement". You create an
instance that by design is used by a single thread only.

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

An instance var that is the same thing as an argument? I have no idea
what that is. You could use thread local variables but I suggest to use
those sparingly as they make code hard to read and understand - explicit
passing is better IMHO. Also, implicit transfer might cause unwanted
side effects (e.g. two completely unrelated pieces of code using the
same name for a thread local).

Kind regards

robert

Trans

6/17/2007 12:27:00 PM

0



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.