Rubyで扱うQueueの活用法10選!

プログラミング初心者がRubyのQueueを理解し、活用できるようになるステップバイステップガイドのイメージRuby
この記事は約13分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

RubyでQueueを扱いたいけどどうすればいいかわからない…

そんな初心者の方も安心してください。

この記事では、プログラミング初心者でも理解しやすいように、RubyのQueueとその操作方法をステップバイステップで解説しています。

実際のサンプルコードを交えながら、その使い方、対処法、注意点、そしてカスタマイズ方法を詳細に見ていきましょう。

この記事を読めば、RubyでのQueue操作ができるようになるはずです。

●Queueとは

Queue(キュー)とは、データ構造の一つで「先入れ先出し」(First In First Out、略してFIFO)の特性を持っています。

これは、データを追加する「enqueue(エンキュー)」操作とデータを取り出す「dequeue(デキュー)」操作があり、最初に追加されたデータが最初に取り出されるという特性を指します。

RubyのQueueクラスはスレッド間でのデータのやりとりを安全に行うためのクラスで、マルチスレッド環境でも安全に使用することができます。

○Queueの生成方法

RubyでQueueのインスタンスを生成する方法は簡単です。

# Queueの生成
queue = Queue.new

このコードではQueue.newを使って、Queueの新しいインスタンスを生成しています。

生成したインスタンスは変数queueに代入されています。

●Queueの操作

次に、Queueでできる基本的な操作とそのサンプルコードを見ていきましょう。

○enqueue操作とそのサンプルコード

まずはデータをQueueに追加するenqueue操作です。

この操作はQueueの最後にデータを追加します。

queue = Queue.new

# データの追加
queue.enqueue("Ruby")
queue.enqueue("Python")
queue.enqueue("JavaScript")

# Queueの内容を表示
p queue  # => #<Thread::Queue:0x00007fa51e123d00 @que=["Ruby", "Python", "JavaScript"], @waitq=[]>

このコードでは、最初にQueueの新しいインスタンスを生成しています。

その後、enqueueメソッドを用いて、Queueに”Ruby”、”Python”、”JavaScript”の3つのデータを追加しています。

最後にpメソッドでQueueの内容を表示しています。表示結果から、追加した順にデータがQueueに格納されていることが確認できます。

○dequeue操作とそのサンプルコード

次に、Queueからデータを取り出すdequeue操作について見ていきましょう。

dequeue操作ではQueueの先頭からデータが取り出されます。

queue = Queue.new

# データの追加
queue.enqueue("Ruby")
queue.enqueue("Python")
queue.enqueue("JavaScript")

# データの取り出し
data = queue.dequeue

# 取り出したデータとQueueの内容を表示
puts data  # => Ruby
p queue  # => #<Thread::Queue:0x00007fa51e123d00 @que=["Python", "JavaScript"], @waitq=[]>

このサンプルコードでは、最初にenqueue操作で”Ruby”、”Python”、”JavaScript”の3つのデータをQueueに追加しています。

その後、dequeueメソッドでQueueからデータを取り出し、取り出したデータは変数dataに代入されています。

最後に、取り出したデータとQueueの内容を表示しています。

この表示結果から、最初に追加したデータが最初に取り出されるQueueの先入れ先出し(FIFO)の特性が確認できます。

○empty?操作とそのサンプルコード

Queueが空かどうかを確認するにはempty?メソッドを使用します。

下記のサンプルコードでは、Queueが空かどうかを確認し、その結果を表示しています。

queue = Queue.new

# Queueが空かどうか確認
p queue.empty?  # => true

# データの追加
queue.enqueue("Ruby")

# 再度Queueが空かどうか確認
p queue.empty?  # => false

このコードでは、最初に新しいQueueを生成し、その後empty?メソッドでQueueが空かどうかを確認しています。

その結果はtrueとなり、Queueが空であることが確認できます。

次に、Queueに”Ruby”を追加し、再度empty?メソッドでQueueが空かどうかを確認しています。

その結果はfalseとなり、Queueにデータが存在することが確認できます。

○clear操作とそのサンプルコード

Queueから全てのデータを削除するにはclearメソッドを使用します。

下記のサンプルコードでは、Queueに追加したデータを全て削除し、その結果を表示しています。

queue = Queue.new

# データの追加
queue.enqueue("Ruby")
queue.enqueue("Python")
queue.enqueue("JavaScript")

# Queueの内容を表示
p queue  # => #<Thread::

Queue:0x00007fa51e123d00 @que=["Ruby", "Python", "JavaScript"], @waitq=[]>

# Queueから全てのデータを削除
queue.clear

# 再度Queueの内容を表示
p queue  # => #<Thread::Queue:0x00007fa51e123d00 @que=[], @waitq=[]>

このコードでは、まず3つのデータをQueueに追加し、その内容を表示しています。

その後、clearメソッドでQueueから全てのデータを削除し、再度Queueの内容を表示しています。

最終的なQueueの内容から、全てのデータが削除されていることが確認できます。

●Queueの応用例

これまでRubyのQueueの基本的な操作について学んできました。

Queueはその性質上、様々な場面で利用されます。ここでは、Queueの具体的な応用例として、並列処理、データストリームの管理、タスクスケジューリングについて見ていきます。

○サンプルコード1:並列処理でのQueueの利用

Queueは並列処理を行う際に、スレッド間でデータをやり取りするために使われます。

require 'thread'
queue = Queue.new

# スレッドの生成
producer = Thread.new do
  10.times do |i|
    sleep rand(i) # ランダムな時間を待つ
    queue << i
    puts "#{i}を追加"
  end
end

consumer = Thread.new do
  10.times do |i|
    value = queue.pop
    sleep rand(i/2) # ランダムな時間を待つ
    puts "#{value}を取り出し"
  end
end

# スレッドの終了を待つ
producer.join
consumer.join

このサンプルコードでは、データの生成と取り出しをそれぞれ異なるスレッドで行う並列処理を行っています。

データの生成を行うスレッドは0から9までの数値をランダムな時間を開けてQueueに追加し、その都度、追加した数値を表示します。

一方、データの取り出しを行うスレッドはQueueからデータを取り出し、その都度、取り出した数値を表示します。

Queueを利用することで、データの生成と取り出しをそれぞれ異なるタイミングで行うことが可能となります。

○サンプルコード2:データストリームの管理

Queueはデータストリームの管理にも利用されます。

データストリームとは連続的なデータの流れのことで、リアルタイムの通信やストリーミング配信などで利用されます。

require 'thread'
queue = Queue.new

# ストリームデータの追加
Thread.new do
  while (line = gets)
    queue << line.chomp
  end
end

# ストリームデータの処理
while (line = queue.pop)
  puts "受け取ったデータ: #{line}"
  # ここで適切な処理を行う
end

このサンプルコードでは、キーボードからの入力をストリームデータとしてQueueに追加し、追加されたデータを順に取り出して処理しています。

キーボードからの入力が連続的に行われることを想定し、それぞれの入力を順に処理することで、データストリームの管理を行っています。

○サンプルコード3:タスクスケジューリング

Queueはタスクのスケジューリングにも利用されます。

タスクスケジューリングとは、複数のタスクを一定の順序や優先度で実行するための管理を行うことです。

require 'thread'
queue = Queue.new

# タスクの追加
Thread.new do
  10.times do |i|
    sleep rand(i)
    queue << "タスク#{i}"
  end
end

# タスクの実行
while (task = queue.pop)
  puts "実行中: #{task}"
  sleep 1 # タスクの実行に時間がかかると仮定
end

このサンプルコードでは、複数のタスクを一定の間隔でQueueに追加し、追加されたタスクを順に取り出して実行しています。

タスクの追加と実行をそれぞれ異なるタイミングで行うことで、タスクのスケジューリングを行っています。

●注意点と対処法

Queueを活用する上で、いくつかの注意点があります。

特にマルチスレッド環境や大量のデータを扱う場合には、事前に注意しておくべき点がいくつか存在します。

○マルチスレッド環境での注意点

RubyのQueueはマルチスレッド環境で安全に使用できるように設計されていますが、スレッド間でデータのやり取りを行う場合には細心の注意が必要です。

下記のコードは、スレッド間でデータのやり取りを行う際の注意点を示す一例です。

require 'thread'
queue = Queue.new

# データの追加
producer = Thread.new do
  5.times do |i|
    sleep rand(i)
    queue << i
    puts "#{i}を追加"
  end
end

# データの取り出し
consumer = Thread.new do
  5.times do |i|
    sleep rand(i)
    value = queue.pop
    puts "#{value}を取り出し"
  end
end

producer.join
consumer.join

このコードでは、データの追加と取り出しをそれぞれ別のスレッドで行っています。

データの追加が先に終了した場合、取り出しを行うスレッドがまだ処理を終えていないと、queue.popは取り出すべきデータが追加されるまで待機します。

これにより、プログラム全体が停止する可能性があるため注意が必要です。

○大量データの扱い方

Queueに大量のデータを追加する際には、メモリの消費に注意が必要です。

下記のコードは、大量のデータを扱う際の注意点を示す一例です。

require 'thread'
queue = Queue.new

# 大量のデータの追加
Thread.new do
  1000000.times do |i|
    queue << "データ#{i}"
  end
end

# データの取り出し
while (data = queue.pop)
  puts "取り出したデータ: #{data}"
  sleep 0.01
end

このコードでは、Queueに大量のデータを追加し、その後で順にデータを取り出しています

しかし、このように大量のデータをQueueに追加すると、メモリの消費が急激に増える可能性があります。

そのため、データの追加と取り出しを適切なタイミングで行うことが重要です。

●Queueのカスタマイズ方法

RubyのQueueは、標準ライブラリの一部として提供されていますが、特定の要件に応じてカスタマイズすることも可能です。

ここでは、一例として、Priority Queueの実装方法について詳しく解説します。

○Priority Queueの実装

Priority Queueは、要素が優先度に基づいて並べられる特殊なQueueです。

最高優先度の要素が最初に取り出されることが保証されています。

RubyでPriority Queueを実装する一例を紹介します。

require 'thread'
require 'forwardable'

class PriorityQueue
  extend Forwardable

  def_delegators :@queue, :clear, :empty?, :length, :size, :num_waiting

  def initialize
    @queue = []
    @mutex = Mutex.new
    @resource = ConditionVariable.new
  end

  def <<(x)
    @mutex.synchronize do
      @queue << x
      @queue.sort!
      @resource.broadcast
    end
  end
  alias push <<
  alias enq <<

  def pop(non_block=false)
    @mutex.synchronize do
      while true
        if !@queue.empty?
          return @queue.shift
        elsif non_block
          raise ThreadError, "queue empty"
        else
          @resource.wait(@mutex)
        end
      end
    end
  end
  alias shift pop
  alias deq pop
end

このコードでは、PriorityQueueクラスを作成しています。

Queueクラスの公開メソッドの一部を継承し、その他のメソッドはカスタマイズしています。

具体的には、<<、push、enqメソッドは要素をQueueに追加し、その後でQueueの要素をソートします。

pop、shift、deqメソッドは、Queueから最も優先度の高い要素(最初の要素)を取り出します。

このPriorityQueueクラスを用いることで、優先度に基づくデータの管理が可能になります。

○サンプルコード:Priority Queueの利用例

このPriorityQueueクラスを使用するサンプルコードを紹介します。

priority_queue = PriorityQueue.new

# データの追加
priority_queue << 5
priority_queue << 3
priority_queue << 4

# データの取り出し
puts priority_queue.pop # => 3
puts priority_queue.pop # => 4
puts priority_queue.pop # => 5

このコードでは、PriorityQueueのインスタンスに3つの要素を追加し、その後で要素を取り出しています。

要素は優先度順(数値の場合は小さいほど優先度が高い)に取り出されます。

まとめ

この記事では、Rubyで扱うQueueの基本的な使い方から、カスタマイズ方法、そしてその活用法まで、初心者でも理解しやすいようにステップバイステップで説明しました。

Queueは、マルチスレッド環境でのデータの同期、大量のデータの管理、優先度に基づくデータの管理など、様々な場面で活用することができます。

Queueを理解し、適切に活用することで、データ管理の効率性を向上させることが可能です。

この記事が、皆さんのRubyプログラミング学習の一助になれば幸いです。