Understanding Threads...

M

Matt White

I am writing an app that retrieves multiple web pages in one method
call. Threading has improved performance drastically for me, but I
need some help understanding how exactly the call to join is going to
affect my program.

Here's some code:

def method(string)
result = {}
mutex = Mutex.new
threads = []

%w{methodname1 methodname2 methodname3 methodname4}.each do |method|
threads << Thread.new(method) do |m|
r = eval("#{m}(string)") # each method call makes an HTTP
request
mutex.synchronize { result.merge!(r) }
end
end
threads.each { |t| t.join }
result
end

Seems like the call to join on each thread is necessary to keep the
script from getting ahead of itself, but if I exclude that line, it
doesn't seem to hurt my results and the program runs a lot faster.
Also, sometimes I get deadlocked somehow if I do use the call to join
and I'm not certain as to why. Can someone help shed some light on the
situation? Do I need to call join? Any idea why I'm deadlocking?
Thanks!
 
J

Jason Roelofs

[Note: parts of this message were removed to make it a legal post.]

I am writing an app that retrieves multiple web pages in one method
call. Threading has improved performance drastically for me, but I
need some help understanding how exactly the call to join is going to
affect my program.

Here's some code:

def method(string)
result = {}
mutex = Mutex.new
threads = []

%w{methodname1 methodname2 methodname3 methodname4}.each do |method|
threads << Thread.new(method) do |m|
r = eval("#{m}(string)") # each method call makes an HTTP
request
mutex.synchronize { result.merge!(r) }
end
end
threads.each { |t| t.join }
result
end

Seems like the call to join on each thread is necessary to keep the
script from getting ahead of itself, but if I exclude that line, it
doesn't seem to hurt my results and the program runs a lot faster.
Also, sometimes I get deadlocked somehow if I do use the call to join
and I'm not certain as to why. Can someone help shed some light on the
situation? Do I need to call join? Any idea why I'm deadlocking?
Thanks!
Thread#join simply says "Wait here until this thread has finished
executing".

So what you're doing is waiting for all threads to finish before execution
continues, aka blocking main thread execution. Without the #join, the values
in results will be nonderministic. Any perceived deadlocking is probably
whatever is in your eval call not timing out. You'll have to watch out
carefully for that.

Jason
 
J

Judson Lester

[Note: parts of this message were removed to make it a legal post.]

I completely agree with Jason's diagnosis. I'd like to make two
observations, though.

First, you can avoid the mutex entirely by using thread-local variables:

threads << Thread.new { Thread.current[:result] = method1(string) }

results = threads.inject({}) do |results, thread|
thread.join
results.merge(thread[:result])
end

Second, and (possibly) more controversially, just because you can eval
doesn't mean you should. To my eye, this looks nicer:

threads = [
Thread.new { Thread.current[:result] = method1(string) },
Thread.new { Thread.current[:result] = method2(string) },
Thread.new { Thread.current[:result] = method3(string) },
Thread.new { Thread.current[:result] = method4(string) }
]

And exception handling, etc, will be ever so much clearer.

Judson

I am writing an app that retrieves multiple web pages in one method
call. Threading has improved performance drastically for me, but I
need some help understanding how exactly the call to join is going to
affect my program.

Here's some code:

def method(string)
result = {}
mutex = Mutex.new
threads = []

%w{methodname1 methodname2 methodname3 methodname4}.each do |method|
threads << Thread.new(method) do |m|
r = eval("#{m}(string)") # each method call makes an HTTP
request
mutex.synchronize { result.merge!(r) }
end
end
threads.each { |t| t.join }
result
end

Seems like the call to join on each thread is necessary to keep the
script from getting ahead of itself, but if I exclude that line, it
doesn't seem to hurt my results and the program runs a lot faster.
Also, sometimes I get deadlocked somehow if I do use the call to join
and I'm not certain as to why. Can someone help shed some light on the
situation? Do I need to call join? Any idea why I'm deadlocking?
Thanks!
Thread#join simply says "Wait here until this thread has finished
executing".

So what you're doing is waiting for all threads to finish before execution
continues, aka blocking main thread execution. Without the #join, the
values
in results will be nonderministic. Any perceived deadlocking is probably
whatever is in your eval call not timing out. You'll have to watch out
carefully for that.

Jason
 
R

Robert Klemme

I completely agree with Jason's diagnosis. I'd like to make two
observations, though.

First, you can avoid the mutex entirely by using thread-local variables:

threads << Thread.new { Thread.current[:result] = method1(string) }

results = threads.inject({}) do |results, thread|
thread.join
results.merge(thread[:result])
end

Even better: we have Thread.value. If you join only from a single
thread there is no additional synchronization needed:

irb(main):001:0> t = (1..5).map {|i| Thread.new(i) {|x| "value #{x}"} }
=> [#<Thread:0x9c4f618 dead>, #<Thread:0x9c4f58c dead>,
#<Thread:0x9c4f4b0 run>, #<Thread:0x9c4f424 run>, #<Thread:0x9c4f398 run>]
irb(main):002:0> t.map {|th| th.value}
=> ["value 1", "value 2", "value 3", "value 4", "value 5"]
Second, and (possibly) more controversially, just because you can eval
doesn't mean you should. To my eye, this looks nicer:

threads = [
Thread.new { Thread.current[:result] = method1(string) },
Thread.new { Thread.current[:result] = method2(string) },
Thread.new { Thread.current[:result] = method3(string) },
Thread.new { Thread.current[:result] = method4(string) }
]

And exception handling, etc, will be ever so much clearer.

You can as well do

def method(string)
threads = %w{
methodname1
methodname2
methodname3
methodname4
}.map do |method|
Thread.new(method) do |m|
send(m, string) # each method call makes an HTTP request
end.map {|th| th.value}
end

and be done.

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,077
Latest member
SangMoor21

Latest Threads

Top