I learned a new trick from my friend and co-worker @DaDDYe, author of the awesome Padrino framework, on how to use EventMachine in normal Ruby or Rails applications.
In most EventMachine apps, you have to place everything inside of a EventMachine.run block, i.e.:
12345
require'eventmachine'EM.rundo# ... your app hereend
This usually means that concurrent Ruby apps have to be designed so from the start. However, you can get around this limitation.
You can start EventMachine in a seperate thread, and then you can use EventMachine calls in your synchronous program. There’s some setup and teardown to make sure everything works:
require'eventmachine'# Setup EventMachineThread.new{EM.run}# Catch signals to ensure a clean shutdown of EventMachinetrap(:INT){EM.stop}trap(:TERM){EM.stop}# Wait for the reactor to startwhilenotEM.reactor_running?;end# Setup done! Now you can use any EventMachine code here!# Examples:channel=EM::Channel.newsub_id=channel.subscribe{|msg|puts"Got: #{msg}"}channel.push('hello world')# => "Got: hello world"# Clean up channelchannel.unsubscribe(sub_id)# Will output the current time every second since it startedEM.add_timer(1){puts"Executing timer: #{Time.now}"}# Deferred blocks execute concurrently to the main threadEM.defer{sleep3;puts"It's been at least 3 seconds!"}# Can even use EventMachine modules and code for async I/OEM.system('ls'){|output,status|putsoutput}# Tear-down, make sure that all EM defers finishwhilenotEM.defers_finished?;end
There’s some obvious advantages to doing this, you can easily bring asynchronous I/O and concurrent processing to any Ruby program, even in normal Rails code. There is a cost, however, and that comes in performance:
bench_results.rb
12345678910111213
# Rehearsal -------------------------------------------------------# normal EventMachine 0.180000 0.000000 0.180000 ( 0.185271)# ---------------------------------------------- total: 0.180000sec# user system total real# normal EventMachine 0.170000 0.000000 0.170000 ( 0.173966)# Rehearsal ---------------------------------------------------------# threaded EventMachine 0.450000 0.730000 1.180000 ( 0.806186)# ------------------------------------------------ total: 1.180000sec# user system total real# threaded EventMachine 0.740000 1.390000 2.130000 ( 1.384526)
Using the threaded approach is a large performance penalty, but the trade off is the additional flexibility. Use it wisely.
require'eventmachine'require'benchmark'Thread.new{EM.run}# Catch signals to ensure a clean shutdown of EventMachinetrap(:INT){EM.stop}trap(:TERM){EM.stop}# Wait for the reactor to startwhilenotEM.reactor_running?;endchannel=EventMachine::Channel.newcounter=0sub_id=channel.subscribe{|msg|counter+=msg}Benchmark.bmbmdo|x|x.report("threaded EventMachine"){50000.times{|i|channel.push(i)}}end