Rubyでプログラミングを行っていると、特定の処理を常時実行させたり、外部の入力を待ち続けたりするために「無限ループ」が必要になる場面があります。

無限ループは正しく実装すれば非常に強力なツールとなりますが、一歩間違えるとCPUリソースの枯渇やプログラムのフリーズを引き起こすリスクも孕んでいます。

この記事では、Rubyにおける無限ループの代表的な実装方法から、安全に処理を制御するためのテクニック、そしてプログラムを停止させるための適切な作法について詳しく解説します。

Rubyにおける無限ループの基本実装

Rubyには無限ループを実現するための方法がいくつか用意されています。

用途に応じて最適な書き方を選択することで、コードの可読性とメンテナンス性を向上させることができます。

loopメソッドによる実装

Rubyにおいて最も推奨される無限ループの実装方法は、loopメソッドを使用することです。

loopメソッドはブロックを受け取り、その中の処理を無限に繰り返します。

Ruby
# 最も一般的なloopメソッドによる実装
loop do
  puts "処理を実行中..."
  # ここに実行したい処理を記述します
  sleep(1) # CPU負荷を抑えるための待機
end

while文やuntil文による実装

他のプログラミング言語と同様に、while文を使用して無限ループを作ることも可能です。

条件式に恒真(常に真となる値)を指定することでループを継続させます。

Ruby
# while文を使用した例
while true
  puts "whileによるループです"
  break # 安全のためにすぐに抜ける例
end

# until文を使用した例
until false
  puts "untilによるループです"
  break
end

ただし、Rubyでは後述する理由によりloopメソッドの使用が推奨されることが多いです。

無限ループを安全に制御する方法

無限ループを実装する上で最も重要なのは、特定の条件でループを終了させる仕組みを持つことです。

意図的にループを抜けるための制御構文を適切に使い分けましょう。

breakによるループの脱出

ループ内で特定の条件が満たされたときに処理を中断するには、breakを使用します。

loopメソッドの中でbreakを呼び出すと、直ちにブロックから脱出します。

Ruby
counter = 0

loop do
  counter += 1
  puts "現在のカウント: #{counter}"

  if counter >= 5
    puts "カウントが上限に達したため終了します"
    break
  end
end
実行結果
現在のカウント: 1
現在のカウント: 2
現在のカウント: 3
現在のカウント: 4
現在のカウント: 5
カウントが上限に達したため終了します

nextによる処理のスキップ

ループ自体は終了させず、現在の回の処理だけをスキップして次の回に進みたい場合はnextを使用します。

特定の条件に合致するデータだけを無視したい場合に有効です。

Ruby
loop do
  data = rand(1..10) # 1から10の乱数

  if data.even?
    puts "#{data}は偶数なのでスキップします"
    next
  end

  puts "奇数データ: #{data} を処理しました"
  break if data == 9 # 9が出たら終了
end

無限ループ停止時の注意点とシグナル処理

サーバーサイドで動作するスクリプトなどの場合、プログラムの外部から停止命令(Ctrl + Cなど)を受け取ることがあります。

この際、唐突にプロセスを終了させるのではなく、終了処理(クリーンアップ)を行ってから停止させるのが安全な実装です。

Signal.trapによる割り込み管理

RubyではSignal.trapを使用することで、特定のOSシグナルを受け取った際の挙動を定義できます。

例えば、INT(割り込み)シグナルを受け取った際にフラグを立てて、ループを安全に終了させることが可能です。

Ruby
running = true

# SIGINT (Ctrl+C) をキャッチ
Signal.trap(:INT) do
  puts "\n割り込みを検知しました。終了準備に入ります..."
  running = false
end

puts "ループを開始します。停止するには Ctrl+C を押してください。"

while running
  print "."
  sleep(0.5)
end

puts "リソースを解放して安全に終了しました。"

begin-ensureによる確実な終了

エラーが発生した場合や強制終了された場合でも、必ず実行したい処理がある場合はensure節を活用します。

ファイルクローズやDB接続の切断などは、このブロックで行うのが定石です。

Ruby
begin
  loop do
    puts "監視中..."
    sleep(2)
  end
ensure
  puts "ログファイルを保存し、接続を閉じました。"
end

パフォーマンスへの配慮:CPUリソースの保護

無限ループを記述する際に初心者が陥りやすい罠が、CPU使用率の異常な上昇です。

待機処理のない無限ループは、CPUの1コアを100%消費し続けてしまいます。

sleepメソッドの重要性

ループ内で重い処理を行わない場合や、一定間隔の監視が目的である場合は、必ずsleepを挿入してください。

わずかsleep(0.1)を入れるだけでも、CPU負荷は劇的に軽減されます。

Ruby
# 悪い例:CPUを消費しすぎる
loop do
  # 何も待機せずにループを回す
  check_status
end

# 良い例:適切な間隔を置く
loop do
  check_status
  sleep(1) # 1秒待機
end

StopIteration例外の活用

Rubyのloopメソッドには、ブロック内でStopIteration例外が発生すると、それを捕捉してループを正常終了させるという特徴があります。

これは、Enumerator(外部イテレータ)をループさせる際に非常に役立ちます。

Ruby
items = [1, 2, 3].to_enum

loop do
  item = items.next # 要素がなくなるとStopIterationを発生させる
  puts item
end
# StopIterationは自動的にrescueされるため、ここでエラーにはならない
puts "すべての要素を処理しました。"

while trueではこの自動的な捕捉が行われないため、Rubyで無限ループを書くならloopメソッドが推奨される大きな理由の一つです。

まとめ

Rubyにおける無限ループは、バックグラウンド処理やリアルタイムな状態監視を実現するために欠かせない技術です。

loopメソッドを基本とし、breakによる適切な脱出条件の設定や、sleepによる負荷軽減を忘れないようにしましょう。

また、外部からの停止命令に対応するためにシグナル処理やensureを組み合わせることで、堅牢なプログラムを構築できます。

安全な無限ループの実装をマスターし、Rubyプログラムの安定性を一段上のレベルへと引き上げてください。