web-dev-qa-db-ja.com

スレッドとキュー

スレッドベースのキューを実装するための最良の方法を知りたいと思っています。

例えば:

4つのスレッドだけで実行したいアクションが10個あります。 10個のアクションすべてを直線的に配置したキューを作成し、最初の4個のアクションを4個のスレッドで開始します。スレッドのいずれかが実行されると、次のアクションが開始されます。つまり、一度にスレッドの数は次のようになります。 4または4未満のいずれか。

27
Pillard

標準ライブラリの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などの完全な同時実行を可能にするインタープリターを使用することもできます。

30
Theo

このパターンを実装するいくつかの宝石があります。パラレル、ピーチ、そして私のものはthreach(またはjruby_threach jrubyの下)。これは#eachのドロップイン置換ですが、実行するスレッドの数を指定できます。その下にあるSizedQueueを使用して、制御不能になるのを防ぎます。

そう...

(1..10).threach(4) {|i| do_my_work(i) }

私自身のものをプッシュしません。物事を簡単にするための優れた実装がたくさんあります。

JRubyを使用している場合は、jruby_threachははるかに優れた実装です-Javaは、使用するスレッドプリミティブとデータ構造のはるかに豊富なセットを提供するだけです。

7
Bill Dueber

実行可能な説明的な例:

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
5
Inversion

スレッドプールを使用できます。これは、このタイプの問題のかなり一般的なパターンです。
http://en.wikipedia.org/wiki/Thread_pool_pattern

Githubには、試してみることができるいくつかの実装があるようです。
https://github.com/search?type=Everything&language=Ruby&q=thread+pool

4
Casper

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
1
AxelWass

セルロイド これを行う ワーカープールの例 があります。

1
maasha