JS-style asynchronous / non-blocking callback execution with Ruby, without heavy machines like threads?

I am an interface developer who is a little familiar with Ruby. I only know how to do Ruby synchronously / sequentially, while in JS I use asynchronous / non-blocking callbacks.

Here is a sample Ruby code:

results = []
rounds = 5

callback = ->(item) {
  # This imitates that the callback may take time to complete
  sleep rand(1..5)

  results.push item

  if results.size == rounds
    puts "All #{rounds} requests have completed! Here they are:", *results
  end
}

1.upto(rounds) { |item| callback.call(item) }

puts "Hello"

The goal is to execute callbacks without blocking the main execution of the script. In other words, I want the line "Hello" to appear in the output above the line "All 5 requests ...". In addition, callbacks must be executed at the same time, so that the callback that completes the fastest will first fall into the resulting array.

With JavaScript, I would simply wrap the callback call in setTimeoutwith a zero delay:

setTimeout( function() { callback(item); }, 0);

JS / concurrency/ . , , .

: , , , .. . .

, setTimeout(). , setTimeout() ( sleep, , ).

, JS- Ruby, :

  • . , , Ruby, :

    • .
    • .
    • , .
    • .
    • .
    • , .

    JavaScript setTimeout() . , , proc .

  • Ruby, Celluloid Event Machine. , , .

  • , (, apeiros @freenode, , , setTimeout ). , . , , , , .

, Ruby , , JS - . , Ruby , JS, .

, : / Ruby, , ?

PS , # 3 apeiros , , .

+4
2

, apeiros asQuirreL, , .

, .

1:

-, JS, :

setTimeout( function() {
  console.log("world");
}, 0);

console.log("hello");

// 'Will print "hello" first, then "world"'.

Ruby:

# You wrap all your code into this...
Branch.new do

  # ...and you gain access to the `branch` method that accepts a block.
  # This block runs non-blockingly, just like in JS `setTimeout(callback, 0)`.
  branch { puts "world!" }

  print "Hello, "

end

# Will print "Hello, world!"

, , . - Branch.new { ... }.

2:

, , .

JS , Ruby:

var
  results = [],
  rounds = 5;

for (var i = 1; i <= rounds; i++) {

  console.log("Starting thread #" + i + ".");

  // "Creating local scope"
  (function(local_i) {
    setTimeout( function() {

      // "Assuming there a time-consuming operation here."

      results.push(local_i);
      console.log("Thread #" + local_i + " has finished.");

      if (results.length === rounds)
        console.log("All " + rounds + " threads have completed! Bye!");

    }, 0);
  })(i);
}

console.log("All threads started!");

:

Starting thread #1.
Starting thread #2.
Starting thread #3.
Starting thread #4.
Starting thread #5.
All threads started!
Thread #5 has finished.
Thread #4 has finished.
Thread #3 has finished.
Thread #2 has finished.
Thread #1 has finished.
All 5 threads have completed! Bye!

, .

, results . JS , Ruby .

Ruby :

Branch.new 1 do

  # Setting up an array to be filled with that many values.
  results = []
  rounds = 5

  # Running `branch` N times:
  1.upto(rounds) do |item|

    puts "Starting thread ##{item}."

    # The block passed to `branch` accepts a hash with mutexes 
    # that you can use to synchronize threads.
    branch do |mutexes|

      # This imitates that the callback may take time to complete.
      # Threads will finish in reverse order.
      sleep (6.0 - item) / 10

      # When you need a mutex, you simply request one from the hash.
      # For each unique key, a new mutex will be created lazily.
      mutexes[:array_and_output].synchronize do
        puts "Thread ##{item} has finished!"
        results.push item

        if results.size == rounds
          puts "All #{rounds} threads have completed! Bye!"
        end
      end
    end
  end

  puts "All threads started."
end

puts "All threads finished!"

, , , .

3:

setTimeout, .

JS

setTimeout(function(){ console.log('Foo'); }, 2000);

branch(2) { puts 'Foo' }

4:

JS , script . /.

Ruby , Branch . Branch.new{}, . , , Branch .

Branch.new do
  branch { sleep 10 }
  branch { sleep 5 }

  # This will be printed immediately
  puts "All threads started!"
end

# This will be printed after 10 seconds (the duration of the slowest branch).
puts "All threads finished!"

Branch.new{} .

# (c) lolmaus (Andrey Mikhaylov), 2014
# MIT license http://choosealicense.com/licenses/mit/

class Branch
  def initialize(mutexes = 0, &block)
    @threads = []
    @mutexes = Hash.new { |hash, key| hash[key] = Mutex.new }

    # Executing the passed block within the context
    # of this class' instance.
    instance_eval &block

    # Waiting for all threads to finish
    @threads.each { |thr| thr.join }
  end

  # This method will be available within a block
  # passed to `Branch.new`.
  def branch(delay = false, &block)

    # Starting a new thread 
    @threads << Thread.new do

      # Implementing the timeout functionality
      sleep delay if delay.is_a? Numeric

      # Executing the block passed to `branch`,
      # providing mutexes into the block.
      block.call @mutexes
    end
  end
end
+1

, , , Threads , . setTimeout, , .

Javascript setTimeout ruby:

require 'thread'
require 'set'

module Timeout
  @timeouts = Set[]
  @exiting = false

  @exitm = Mutex.new
  @mutex = Mutex.new

  at_exit { wait_for_timeouts }

  def self.set(delay, &blk)
    thrd = Thread.start do
      sleep delay
      blk.call
      @exitm.synchronize do
        unless @exiting
          @mutex.synchronize { @timeouts.delete thrd }
        end
      end
    end

    @mutex.synchronize { @timeouts << thrd }
  end

  def self.wait_for_timeouts
    @exitm.synchronize { @exiting = true }
    @timeouts.each(&:join)
    @exitm.synchronize { @exiting = false }
  end
end

, :

$results = []
$rounds = 5

mutex = Mutex.new
def callback(n, mutex)
  -> {
    sleep rand(1..5)

    mutex.synchronize {
      $results << n
      puts "Fin: #{$results}" if $results.size == $rounds
    }
  }
end

1.upto($rounds) { |i| Timeout.set(0, &callback(i, mutex)) }

puts "Hello"

:

Hello
Fin: [1, 2, 3, 5, 4]

, , , , , , , .

:

javascript , - . , .

  • , , . (SET, INC).
  • ( ):
    • SET1 INC1 SET2 INC2
    • SET1 SET2 INC1 INC2
  • - , , , .
+2

All Articles