Ractorをつかって、ユーザー入力とクロックを両方待つプログラムを書いてみます。 練習中であり、Ractorを使ったイケているソースコードではありません。
次のプログラムを実装します。 1秒毎にカウントアップします。 ユーザーがEnterキーを押したらカウンターをリセットします。
完成形
# ユーザー入力を待つRactor input = Ractor.new do loop do gets Ractor.yield :reset end end # クロックイベントを発生するRactor clock = Ractor.new do loop do Ractor.yield nil sleep 1 end end # 初期値 val = 0 loop do # ユーザー入力とクロックを両方待ちます。 _, flag = Ractor.select input, clock # イベントがユーザー入力だったら値をリセットします。 val = 0 if flag == :reset # カウントアップします。 val += 1 p val end
失敗作
処女作
ユーザー入力があったときだけ、カウンターから帰ってくる値を0に戻さそうと思って書きました。
input = Ractor.new do loop do gets Ractor.yield 0 end end counter = Ractor.new do loop do Ractor.yield Ractor.recv + 1 sleep 1 end end counter << 0 loop do _, v = Ractor.select input, counter p v counter << v end
実際はこんな感じにうごきます。
~ docker run --rm -it -v (pwd):/ractor wakaba260/ruby-ractor-dev ruby ractor/input_and_timer.rb 1 2 0 3 1 4
リセットされた値とされていない値が両方出力されます。
Ractorはキューを持っているので、counter << v
した値は上書きされることなく保存されています。
debounceを実装
最新の値だけがほしいので、debounceが実装できれば良さそうです。
input = Ractor.new do loop do gets Ractor.yield 0 end end counter = Ractor.new do v = 0 loop do v = Ractor.recv Ractor.yield v + 1 sleep 1 end end debounce = Ractor.new do prev = Time.now.to_i loop do v = Ractor.recv now = Time.now.to_i if now - prev > 0.1 p v prev = now end end end counter << 0 debounce << 0 loop do _, v = Ractor.select input, counter counter << v debounce << v end
実際には、これも期待通りには動きません。
毎回counter << v
しているので、counterのキューに全部の値が貯まります。
どうやらcounterで1秒に1加算しているのがよくなさそうです。 counterを1秒毎にイベントを発火するclockにし、mainで加算しました。
感想
Ractorインスタンスからグローバルな値を変更できないので、Ractor.yield
を使って値を親に返す必要があります。
その代わり、レールに乗りさせすれば、Ractor.select
を使って、イベントの待ち合わせを簡単に書けます。
スレッドで書くと次のようになります。
queue = Queue.new input = Thread.new do loop do gets queue << :reset end end clock = Thread.new do loop do queue << nil sleep 1 end end val = 0 loop do flag = queue.pop val = 0 if flag == :reset val += 1 p val end
今回の使い方からは「最初からキューを持っているスレッド」というイメージを持ちました。