Ruby Threading

From RubySpec

Jump to: navigation, search

The standard C implementation of Ruby implements threading as green threading, where all threads are serviced by a single OS-level thread and the runtime has full control over the thread lifecycle.

Instantiation

Scheduling

Ruby's thread scheduler is a simple cooperative timeslicing scheduler. Each thread is given in turn at least 10ms to execute undisturbed. After this 10ms period has completed, context may switch to another thread if the currently-executing thread encounters a few specific events:

Thread context switching may occur before
  • a "next" keyword is encountered
  • a "redo" keyword is encountered
  • a "retry" keyword is encountered
Thread context switching may occur after
  • an "Opt N" node [corresponding to NODE_OPT_N] (expand this to explain where Opt N occurs in AST)
  • a "self" keyword is encountered [NODE_SELF]
  • a "nil" keyword is encountered [NODE_NIL]
  • a "true" or "false" keyword is encountered [NODE_TRUE or NODE_FALSE]
  • a "when" structure has completed executing [NODE_WHEN]
  • a "case" element of a "when" structure has completed processing [NODE_CASE]
  • an "until" loop has completed (but not for "while"?) [NODE_UNTIL]
  • a value is assigned the result of a boolean && or || operation that has short-circuited (but not when not assigning?) [NODE_OP_ASGN1 or NODE_OP_ASGN2]
Thread context switching may also occur
  • when calling Thread.yield if other threads are waiting to execute
  • when killing or stopping the current thread
  • when leaving a critical section
And a few more unusual times
  • when when a block yield triggers a "redo" event
  • after every yield during Kernel#loop
  • Every 256 method calls (from rb_call0 in eval.c of Ruby 1.8.5)

It is entirely possible that a given thread will not yield control if it does not encounter any of these particular node in the AST, making the Ruby's threading scheduler somewhat cooperative (i.e. a thread must encounter one of these events in order for context switching to happen).

The 10ms timeout is generally sufficient for most threaded applications; it is a fairly short amount of time for threads to have guaranteed atomicity, and so context switch happen very quickly in practice. It does, however, limit Ruby's ability to scale large numbers of threads on higher-performance platforms. On a high-end system, a 10ms timeslice can be an extremely long time; when running a thousand threads, as might be common in a large massively-concurrent application, it would take at least ten seconds for all threads to get a timeslice. Because of the issues associated with this 10ms timeslice and those associated with green threading in general, concurrency in Ruby is typically achieved by running or spawning additional Ruby processes.

Control

Ruby provides a level of control over threads not typically found on other platforms. This is primarily possible because of its green threading implementation and its ability to have complete control over the lifecycle and scheduling of all spawned threads. Along with the usual yield, join, and sleep operations common to other threading implementations, Ruby also allows the following less-common features:

  • Threads can be killed at will from anywhere in the program. It is not clear whether killed threads clean up their references or garbage safely, or what state their IO channels are left in.
  • Threads can be stopped indefinitely (an "infinite sleep")
  • A thread can enter a critical section at any time, preventing all other threads from being scheduled for the duration of that section
  • Aside from critical sections, all threading lifecycle changes can be initiated by the thread itself or by any other thread in the system.

These features have been found unsafe and incompatible with native OS-level threads on many platforms, and so implementing them in alternative Ruby implementations has frequently proven challenging. See JRuby Threading for more.

Personal tools