ふつう、プログラム中のループは全力で回しますが、ときに減速させたくなることもあります。
背景
たとえば外部APIを使う場合など、一定時間内にAPIを叩ける回数が決まっていることがあります。また、そうでなくてもWebスクレイピングなどは速度を手加減してやらないと迷惑です。
もちろん、「1秒間に100アクセスできるAPIを、使い切らないギリギリぐらいのペースで仕事をしたい」というような、ある程度のスケールがあるような用事であれば、Sidekiqを立てて複数スレッドをスケジューリングするなど、きっちりした仕組みを構築する必要があるでしょう。
ただ、逆に「1回あたり100ミリ秒もかからない仕事だけど、3秒に1回のペースでしたい」というような状況であれば、メインスレッド1本で処理をさせれば充分ですし、外部の仕組みを立てるには大げさすぎます。
意外と簡単な別スレッド
ややこしい印象のあるスレッドですが、Rubyでは1行で作れてしまいます。
t = Thread.new { 実行するコード }
もちろん、スレッドを作りっぱなしでは役に立ちませんし、スレッドが外部と連携して動作しようとすれば一気に複雑化します。とはいえ、今回の「時間待ち」程度であれば、簡単に片付きます。
目的のコード
some_collection.each do |obj|
t = Thread.new { sleep 3 }
# objを使ったメイン処理
t.join
end
まず、メインの処理を行う前に、時間測定用のスレッドを立てて、バックで動かしておきます。そして、最後に.join
すると、「スレッドの終了まで待つ」という動作になりますので、指定時間が経過するまでループが進まなくなります。なお、すでに終了したスレッドに.join
しても特にエラーとなることはなく、呼び出し側にすぐ戻ります。
スレッドへの引数
状況によって待機時間を変えたい場合、スレッドに値を渡す必要があります。ただし、無遠慮に外側の変数を渡してしまうと、内側のスレッドが読むタイミングでは値が変化している、ということがありえます。そこで、スレッド開始時に引数として渡す必要があります。
sleep_time = obj.some_method
t = Thread.new(sleep_time) { |st| sleep st }
なお、両スレッドでオブジェクトとして共有されるので、ミュータブルなものを渡す場合には注意が必要です(いミュータブルな、数値やシンボルでは特に問題ないですが)。