LoginSignup
46
43

More than 5 years have passed since last update.

Ruby: Thread と Fiber

Posted at

Ruby のスレッドとファイバの簡単なメモです。

Thread

子スレッドで"hello"を3回表示する例

t1.rb
th = Thread.new { 3.times { puts "hello" ; sleep 1 } }

th.join       # メインスレッドが終了しないように子スレッドを待つ
$ ruby t1.rb 
hello             # 子スレッドで表示
hello             #     hello             #     

thread1.png


子スレッドから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             #       

thread2.png


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)

thread3.png

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

fiber1.png

おわりに

本稿内容の動作確認は以下の環境で行っています。

  • Ruby 2.1.5 p273
46
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
43