I'm working on a use case where I need to spawn a thread that handles a
blocking piece of communication and have it call back into a lamba
function when the call completes. All without blocking the main
application thread.
The code I have is:
class Foo
def sync(args = {}, &callback)
block = args[:block] || false
if block
Thread.new do
# passing in true says to block until the underlying code ends
@underlying_impl.sync true
callback.call
end
else
@underlying_impl.sync false
callback.call
end
end
end
What I _want_ to have happen is that, if I call this with :block =>
true, the blocking call would get spawned off on a separate thread and
run in parallel to the main thread. And, of course, if called with
:block => false (or without :block at all) that the calling thread would
block until the sync exits.
However, what I'm seeing is that, even with :block => true, the new
thread is created but the main thread is blocking. If I put a
Thread.pass call in just before calling @underlying_impl.sync then the
main thread updates but the other thread never executes.
Am I misunderstanding Threading in Ruby? In Java spawning a Thread has
that other thread run in parallel without interaction. Does Ruby not
work the same way?
···
--
Darryl L. Pierce <mcpierce@gmail.com>
http://mcpierce.multiply.com/
"What do you care what people think, Mr. Feynman?"
Hello Darry,
Is your block using some C extension? In ruby<1.9, threads in ruby do
not use system threads. So, if you call
some external C command, no thread will run in ruby. In ruby>=1.9, it
uses pthreads. Even though, threads in
ruby does not run in parallel. There is a global mutex that makes
every thread runs isolated. Just like a single processor
machine.
In the past, while programming with Qt in Ruby, I did a simple hack in
order to get ruby some processor time when main thread goes loop.
block=Proc.new{ Thread.pass }
timer=Qt::Timer.new(window)
invoke=Qt::BlockInvocation.new(timer, block, "invoke()")
Qt::Object.connect(timer, SIGNAL("timeout()"), invoke, SLOT("invoke()"))
timer.start(10)
Regards,
···
---
Luiz Angelo Daros de Luca, Me.
luizluca@gmail.com
2011/6/27 Darryl L. Pierce <mcpierce@gmail.com>:
I'm working on a use case where I need to spawn a thread that handles a
blocking piece of communication and have it call back into a lamba
function when the call completes. All without blocking the main
application thread.
The code I have is:
class Foo
def sync(args = {}, &callback)
block = args[:block] || false
if block
Thread.new do
# passing in true says to block until the underlying code ends
@underlying_impl.sync true
callback.call
end
else
@underlying_impl.sync false
callback.call
end
end
end
What I _want_ to have happen is that, if I call this with :block =>
true, the blocking call would get spawned off on a separate thread and
run in parallel to the main thread. And, of course, if called with
:block => false (or without :block at all) that the calling thread would
block until the sync exits.
However, what I'm seeing is that, even with :block => true, the new
thread is created but the main thread is blocking. If I put a
Thread.pass call in just before calling @underlying_impl.sync then the
main thread updates but the other thread never executes.
Am I misunderstanding Threading in Ruby? In Java spawning a Thread has
that other thread run in parallel without interaction. Does Ruby not
work the same way?
--
Darryl L. Pierce <mcpierce@gmail.com>
http://mcpierce.multiply.com/
"What do you care what people think, Mr. Feynman?"
The details of how threading in Ruby works really depends on the
implementation of Ruby that you are using.
In MRI Ruby < 1.9, threads were green threads, implemented with the
Ruby process itself. CPU intensive code, and threads that invoke
external C functions will not benefit from these threads (generally).
In MRI Ruby 1.9.x, threads are real system threads. However, there is
still a global interpreter lock that ensures that only one Ruby thread
is running at a time. It is possible for a C extension to be running
it's code at the same time that Ruby is executing, however, if the C
extension is written to do that.
In JRuby, threads are regular Java threads, and behave the same way.
In Rubinius, threads are like 1.9.x threads, BUT Rubinius is removing
the single global lock, so in the future, the production releases of
Rubinius will have fully concurrent Ruby threads.
Kirk Haines
Software Engineer
Engine Yard
···
On Mon, Jun 27, 2011 at 9:01 AM, Darryl L. Pierce <mcpierce@gmail.com> wrote:
I'm working on a use case where I need to spawn a thread that handles a
blocking piece of communication and have it call back into a lamba
function when the call completes. All without blocking the main
application thread.
The code I have is:
class Foo
def sync(args = {}, &callback)
block = args[:block] || false
if block
Thread.new do
# passing in true says to block until the underlying code ends
@underlying_impl.sync true
callback.call
end
else
@underlying_impl.sync false
callback.call
end
end
end
What I _want_ to have happen is that, if I call this with :block =>
true, the blocking call would get spawned off on a separate thread and
run in parallel to the main thread. And, of course, if called with
:block => false (or without :block at all) that the calling thread would
block until the sync exits.
However, what I'm seeing is that, even with :block => true, the new
thread is created but the main thread is blocking. If I put a
Thread.pass call in just before calling @underlying_impl.sync then the
main thread updates but the other thread never executes.
Am I misunderstanding Threading in Ruby? In Java spawning a Thread has
that other thread run in parallel without interaction. Does Ruby not
work the same way?
Is your block using some C extension?
Yep, C++.
We have our primary codebase written in C++. We then use Swig to
generate language bindings for Python, Java and Ruby so that we don't
try to reimplement features in each supported language.
So now the question is whether it's worth it for 1.8 to try and work
around this issue or whether it's better to just make the Ruby
developers aware that calling the blocking operations will result in the
main thread being blocked...
In ruby<1.9, threads in ruby do
not use system threads. So, if you call
some external C command, no thread will run in ruby. In ruby>=1.9, it
uses pthreads.
So in 1.9 we should have _some_ semblance of multi-threading with
extensions?
···
On Tue, Jun 28, 2011 at 02:28:07AM +0900, Luiz Angelo Daros de Luca wrote:
--
Darryl L. Pierce <mcpierce@gmail.com>
http://mcpierce.multiply.com/
"What do you care what people think, Mr. Feynman?"
Yes, if you write your extension to take advantage of it. Do some
searches for information on rb_thread_blocking_region(), and take a
look at the source code for it in thread.c. Here are the relevant
comments:
/*
* rb_thread_blocking_region - permit concurrent/parallel execution.
···
On Mon, Jun 27, 2011 at 12:06 PM, Darryl L. Pierce <mcpierce@gmail.com> wrote:
On Tue, Jun 28, 2011 at 02:28:07AM +0900, Luiz Angelo Daros de Luca wrote:
Is your block using some C extension?
In ruby<1.9, threads in ruby do
not use system threads. So, if you call
some external C command, no thread will run in ruby. In ruby>=1.9, it
uses pthreads.
So in 1.9 we should have _some_ semblance of multi-threading with
extensions?
*
* This function does:
* (1) release GVL.
* Other Ruby threads may run in parallel.
* (2) call func with data1.
* (3) aquire GVL.
* Other Ruby threads can not run in parallel any more.
*
* If another thread interrupts this thread (Thread#kill, signal deliverly,
* VM-shutdown request, and so on), `ubf()' is called (`ubf()' means
* "un-blocking function"). `ubf()' should interrupt `func()' execution.
*
* There are built-in ubfs and you can specify these ubfs.
* However, we can not guarantee our built-in ubfs interrupt
* your `func()' correctly. Be careful to use rb_thread_blocking_region().
*
* * RUBY_UBF_IO: ubf for IO operation
* * RUBY_UBF_PROCESS: ubf for process operation
*
* NOTE: You can not execute most of Ruby C API and touch Ruby objects
* in `func()' and `ubf()' because current thread doesn't acquire
* GVL (cause synchronization problem). If you need to do it,
* read source code of C APIs and confirm by yourself.
*
* NOTE: In short, this API is difficult to use safely. I recommend you
* use other ways if you have. We lack experiences to use this API.
* Please report your problem related on it.
*
* Safe C API:
* * rb_thread_interrupted() - check interrupt flag
* * ruby_xalloc(), ruby_xrealloc(), ruby_xfree() -
* if they called without GVL, acquire GVL automatically.
*/
Kirk Haines
Software Engineer
Engine Yard
An alternative, which may or may not be appropriate for your use case,
is to arrange for the C++ part of your application to be executed in a
separate process instead of a separate thread. You'll need to figure
out the inter-process communication but you'll gain concurrency since
processes can be scheduled by the OS to run concurrently on different
processors and Ruby's global lock won't get in the way.
Gary Wright
···
On Jun 27, 2011, at 2:06 PM, Darryl L. Pierce wrote:
So now the question is whether it's worth it for 1.8 to try and work
around this issue or whether it's better to just make the Ruby
developers aware that calling the blocking operations will result in the
main thread being blocked...