From: Robert Klemme Date: 2007-06-17T19:50:04+09:00 Subject: Re: Reasonable practice? On 16.06.2007 06:50, Trans wrote: > > On Jun 15, 11:02 pm, "Erwin Abbott" wrote: >> On 6/15/07, Robert Dober wrote:> On 6/15/07, Trans 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