Ruby のスレッドとファイバの簡単なメモです。
- class Thread (Ruby 2.1.0 リファレンスマニュアル)
- class Fiber (Ruby 2.1.0 リファレンスマニュアル)
- library fiber (Ruby 2.1.0 リファレンスマニュアル)
Thread
子スレッドで"hello"を3回表示する例
t1.rb
th = Thread.new { 3.times { puts "hello" ; sleep 1 } }
th.join # メインスレッドが終了しないように子スレッドを待つ
$ ruby t1.rb
hello # 子スレッドで表示
hello # 〃
hello # 〃
子スレッドからQueue経由で受け取った"hello"をメインスレッドで3回表示する例
t2.rb
q = Queue.new
th = Thread.new { loop { q << "hello" ; sleep 1 } }
3.times { puts q.pop }
$ ruby t2.rb
hello # メインスレッドで表示
hello # 〃
hello # 〃
2つの子スレッドからQueue経由で受け取った"hello(*)"をメインスレッドで10回表示する例
t3.rb
q = Queue.new
m = Mutex.new # Mutex で Queue を排他ロックする
class << m
alias call synchronize # ロックは synchronize でかけるが
end # 名前が長いのでここでは call に alias した
th1 = Thread.new { loop { m.() { q << "hello(1)" } ; sleep 1 } }
th2 = Thread.new { loop { m.() { q << "hello(2)" } ; sleep 3 } }
10.times { puts q.pop }
$ ruby t3.rb
hello(1) # メインスレッドで表示 (以下、同じ)
hello(2)
hello(1)
hello(1)
hello(2)
hello(1)
hello(1)
hello(1)
hello(2)
hello(1)
timeout
標準添付ライブラリの timeout はスレッドで実装されています。
以下は、TCP接続のタイムアウトを検出する例です。
t4.rb
require 'socket'
require 'resolv-replace' # この require により、Ruby が割り込みを行えるように
# DNS名前解決に resolv ライブラリを使うようにする
require 'timeout'
begin
sock = timeout(10) { TCPSocket.open 'localhost', 80 }
rescue TimeoutError => e # タイムアウトした場合 TimeoutError 例外があがる
$stderr.puts e
end
p sock
socket ライブラリは DNS 名前解決に C 言語実装のリゾルバを使用しています。
C 言語実装のリゾルバでは、Ruby がスレッド割り込みできません。
require 'resolv-replace'
することで、C 言語実装のリゾルバの代わりに Ruby の resolv ライブラリを使用します。
そのことで Ruby がスレッド割り込みを行えるようにしています。
上の例を簡易に書いた場合
t4a.rb
require 'socket'
require 'resolv-replace'
require 'timeout'
# タイムアウトも含めコネクトできない場合はすべて sock を nil にする
sock = timeout(10) { TCPSocket.open 'localhost', 80 } rescue nil
p sock
Fiber
"hello"を2回表示するファイバ(コルーチン)を3回繰り返し呼び出す例
t5.rb
require 'fiber' # Fiber#transfer 等を使用する場合に require が必要。
# この例では、この require はなくてよい
f = Fiber.new do
loop do
2.times { puts "hello" ; sleep 1 }
puts "--> main" ; Fiber.yield # Fiber.#yield で元のファイバにコンテキストを戻す
end
end
3.times { puts "--> child" ; f.resume } # resume メソッドでファイバにコンテキストを切り替え
$ ruby t5.rb
--> child
hello
hello
--> main
--> child
hello
hello
--> main
--> child
hello
hello
--> main
おわりに
本稿内容の動作確認は以下の環境で行っています。
- Ruby 2.1.5 p273