Custom Control Structures

Ruby’s use of blocks, coupled with its parentheses-optional syntax, make it very easy to define iterator methods that look like and behave like control structures. The loop method of Kernel is a simple example. In this section we develop three more examples. The examples here use Ruby’s threading API; you may need to read Threads and Concurrency to understand all the details.

Delaying and Repeating Execution: after and every

Example 8-1 defines global methods named after and every. Each takes a numeric argument that represents a number of seconds and should have a block associated with it. after creates a new thread and returns the Thread object immediately. The newly created thread sleeps for the specified number of seconds and then calls (with no arguments) the block you provided. every is similar, but it calls the block repeatedly, sleeping the specified number of seconds between calls. The second argument to every is a value to pass to the first invocation of the block. The return value of each invocation becomes the value passed for the next invocation. The block associated with every can use break to prevent any future invocations.

Here is some example code that uses after and every:

require 'afterevery'

1.upto(5) {|i| after i { puts i} }  # Slowly print the numbers 1 to 5
sleep(5)                            # Wait five seconds
every 1, 6 do |count|               # Now slowly print 6 to 10
  puts count
  break if count == 10
  count + 1                         # The next value of count
end
sleep(6)                            # Give the above time to run

The sleep call at the end of this code prevents the example program from exiting before the thread created by every can complete its count. With that example of how after and every are used, we are now ready to present their implementation. Remember to consult Threads and Concurrency if you don’t understand Thread.new.

Example 8-1. The after and every methods

#
# Define Kernel methods after and every for deferring blocks of code.
# Examples:
#
#   after 1 { puts "done" }
#   every 60 { redraw_clock }
#
# Both methods return Thread objects. Call kill on the returned objects
# to cancel the execution of the code.
#
# Note that this is a very naive implementation. A more robust
# implementation would use a single global timer thread for all tasks,
# would allow a way to retrieve the value of a deferred block, and would
# provide a way to wait for all pending tasks to complete.
#

# Execute block after sleeping the specified number of seconds.
def after(seconds, &block)  
  Thread.new do     # In a new thread...
    sleep(seconds)  # First sleep 
    block.call      # Then call the block
  end               # Return the Thread object right away
end

# Repeatedly sleep and then execute the block.
# Pass value to the block on the first invocation.  
# On subsequent invocations, pass the value of the previous invocation.
def every(seconds, value=nil, &block)
  Thread.new do                 # In a new thread...
    loop do                     # Loop forever (or until break in block)
      sleep(seconds)            # Sleep
      value = block.call(value) # And invoke block
    end                         # Then repeat..
  end                           # every returns the Thread
end

Thread Safety with Synchronized Blocks

When writing programs that use multiple threads, it is important that two threads do not attempt to modify the same object at the same time. One way to do this is to place the code that must be made thread-safe in a block associated with a call to the synchronize method of a Mutex object. Again, this is discussed in detail in Threads and Concurrency. In Example 8-2 we take this a step further, and emulate Java’s synchronized keyword with a global method named synchronized. This synchronized method expects a single object argument and a block. It obtains a Mutex associated with the object, and uses Mutex.synchronize to invoke the block. The tricky part is that Ruby’s object, unlike Java’s objects, do not have a Mutex associated with them. So Example 8-2 also defines an instance method named mutex in Object. Interestingly, the implementation of this mutex method uses synchronized in its new keyword-style form!

Example 8-2. Simple synchronized blocks

require 'thread'  # Ruby 1.8 keeps Mutex in this library

# Obtain the Mutex associated with the object o, and then evaluate
# the block under the protection of that Mutex.
# This works like the synchronized keyword of Java.
def synchronized(o)
  o.mutex.synchronize { yield }
end

# Object.mutex does not actually exist. We've got to define it.
# This method returns a unique Mutex for every object, and
# always returns the same Mutex for any particular object.
# It creates Mutexes lazily, which requires synchronization for
# thread safety.
class Object
  # Return the Mutex for this object, creating it if necessary.
  # The tricky part is making sure that two threads don't call
  # this at the same time and end up creating two different mutexes.
  def mutex
    # If this object already has a mutex, just return it
    return @__mutex if @__mutex
    
    # Otherwise, we've got to create a mutex for the object.
    # To do this safely we've got to synchronize on our class object.
    synchronized(self.class) {
      # Check again: by the time we enter this synchronized block,
      # some other thread might have already created the mutex.
      @__mutex = @__mutex || Mutex.new
    }
    # The return value is @__mutex
  end
end

# The Object.mutex method defined above needs to lock the class 
# if the object doesn't have a Mutex yet. If the class doesn't have
# its own Mutex yet, then the class of the class (the Class object)
# will be locked. In order to prevent infinite recursion, we must
# ensure that the Class object has a mutex.
Class.instance_eval { @__mutex = Mutex.new }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.44.229