スレッドベースのキューを実装するための最良の方法を知りたいと思っています。
例えば:
4つのスレッドだけで実行したいアクションが10個あります。 10個のアクションすべてを直線的に配置したキューを作成し、最初の4個のアクションを4個のスレッドで開始します。スレッドのいずれかが実行されると、次のアクションが開始されます。つまり、一度にスレッドの数は次のようになります。 4または4未満のいずれか。
標準ライブラリのQueue
にはthread
クラスがあります。これを使用すると、次のようなことができます。
require 'thread'
queue = Queue.new
threads = []
# add work to the queue
queue << work_unit
4.times do
threads << Thread.new do
# loop until there are no more things to do
until queue.empty?
# pop with the non-blocking flag set, this raises
# an exception if the queue is empty, in which case
# work_unit will be set to nil
work_unit = queue.pop(true) rescue nil
if work_unit
# do work
end
end
# when there is no more work, the thread will stop
end
end
# wait until all threads have completed processing
threads.each { |t| t.join }
ノンブロッキングフラグを使用してポップする理由は、until queue.empty?
とポップの間に別のスレッドがキューをポップした可能性があるため、ノンブロッキングフラグが設定されていない限り、その行で永久にスタックする可能性があるためです。
MRIを使用している場合、デフォルトのRubyインタープリター、スレッドは完全に同時ではないことに注意してください。作業がCPUにバインドされている場合は、シングルスレッドを実行することもできます。 IOでブロックする操作は、並列処理を行う場合がありますが、YMMVです。または、jRubyやRubiniusなどの完全な同時実行を可能にするインタープリターを使用することもできます。
このパターンを実装するいくつかの宝石があります。パラレル、ピーチ、そして私のものはthreach
(またはjruby_threach
jrubyの下)。これは#eachのドロップイン置換ですが、実行するスレッドの数を指定できます。その下にあるSizedQueueを使用して、制御不能になるのを防ぎます。
そう...
(1..10).threach(4) {|i| do_my_work(i) }
私自身のものをプッシュしません。物事を簡単にするための優れた実装がたくさんあります。
JRubyを使用している場合は、jruby_threach
ははるかに優れた実装です-Javaは、使用するスレッドプリミティブとデータ構造のはるかに豊富なセットを提供するだけです。
実行可能な説明的な例:
require 'thread'
p tasks = [
{:file => 'task1'},
{:file => 'task2'},
{:file => 'task3'},
{:file => 'task4'},
{:file => 'task5'}
]
tasks_queue = Queue.new
tasks.each {|task| tasks_queue << task}
# run workers
workers_count = 3
workers = []
workers_count.times do |n|
workers << Thread.new(n+1) do |my_n|
while (task = tasks_queue.shift(true) rescue nil) do
delay = Rand(0)
sleep delay
task[:result] = "done by worker ##{my_n} (in #{delay})"
p task
end
end
end
# wait for all threads
workers.each(&:join)
# output results
puts "all done"
p tasks
スレッドプールを使用できます。これは、このタイプの問題のかなり一般的なパターンです。
http://en.wikipedia.org/wiki/Thread_pool_pattern
Githubには、試してみることができるいくつかの実装があるようです。
https://github.com/search?type=Everything&language=Ruby&q=thread+pool
work_queue というgemを使用しています。その本当に実用的です。
例:
require 'work_queue'
wq = WorkQueue.new 4, 10
(1..10).each do |number|
wq.enqueue_b("Thread#{number}") do |thread_name|
puts "Hello from the #{thread_name}"
end
end
wq.join