酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

Rubyで並列処理が簡単にできるgem Parallel

生まれて初めて並行処理を書きました!個人的に、プログラム初心者なりにやっと書く機会に巡り会えたかとちょっと感激していますw、ということでプログラム初心者レベル(僕)にも並行処理を簡単に書くことができるGem 『parallel』のご紹介です!


Gemのインストール

Gemfileに以下を追加して、コンソールでbundleを実行してください。

1
2
# 並行処理のサポート
gem 'parallel'

使い方

実際にparallelを使った例として、2スレッドで画像をダウンロードするコードを書いてみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'rubygems'
require 'parallel'
require 'open-uri'
require 'digest/md5'

urls = [
    'http://farm4.staticflickr.com/3052/3086132328_e2041be795.jpg',
    'http://farm7.staticflickr.com/6053/6312937936_cebaf2feb9.jpg',
    'http://farm1.staticflickr.com/54/131841577_0e67642c02.jpg',
    'http://farm3.staticflickr.com/2293/2266151759_058e732577.jpg'
]

Parallel.each(urls, in_threads: 2) {|url|
  puts "start download: #{url}"
  img = open(image) rescue next
  img.close
  file_path = "./#{Digest::MD5.hexdigest(url)}.jpg"
  File.rename(img.path, file_path)
  puts "end download: #{url}"
}

in_threadsの引数を増やせば、スレッド数を増やすことができます。

たったこれだけで並行処理が簡単にかけてしまう、『parallel』。本当にオススメです!

公式サイト

parallelの公式Githubです。

grosser/parallel · GitHub

補足: Queueを使ったマルチスレッド

この記事のコメントで、クズさんから教えて頂いたQueueを使ったマルチスレッドです。空のQueueを読み込んだらスレッドが停止してくれます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
require 'rubygems'
require 'digest/md5'
require 'open-uri'
require 'thread'

urls = [
    'http://farm4.staticflickr.com/3052/3086132328_e2041be795.jpg',
    'http://farm7.staticflickr.com/6053/6312937936_cebaf2feb9.jpg',
    'http://farm1.staticflickr.com/54/131841577_0e67642c02.jpg',
    'http://farm3.staticflickr.com/2293/2266151759_058e732577.jpg',
    nil
]

q = Queue.new
urls.each { |url| q.push(url) }

max_thread = 2 # 最大スレッド数
# max_threadで指定した数だけスレッドを開始
Array.new(max_thread) do |i|
  Thread.new { # スレッドを作成
    begin
      # 最後のnilになったらfalseになって終了
      while url = q.pop(true)
        puts "start download: #{url}"
        img = open(url) rescue next
        img.close
        file_path = "./#{Digest::MD5.hexdigest(url)}.jpg"
        File.rename(img.path, file_path)
        puts "end download: #{url}"
      end
      q.push nil # 最後を表すnilを別スレッドのために残しておく
    end
  }
end.each(&:join)
puts "finish process"

(2013/05/06 17:20) コメントで「通りすがりさん」から例外を発生させずに終了させる方法を伺って、変更しました。コメントありがとうございます!

Queue - Rubyリファレンスマニュアル

[Ruby] スレッド数を固定で並列処理 | nownablog

Rubyを用いたマルチスレッド対応Queueの作り方 - M-Tea

Queueを使ったワーカースレッド - うなの日記

補足: Ruby自体のマルチスレッド機能

Prallelは簡単にマルチスレッド処理を書くことが出来る素晴らしいGemですが、僕が現在作っている仕組みではうまく動いてくれませんでした。(原因は調査中です)ということで、Ruby側のマルチスレッドで書きなおしてみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require 'rubygems'
require 'open-uri'
require 'digest/md5'

urls = [
    'http://farm4.staticflickr.com/3052/3086132328_e2041be795.jpg',
    'http://farm7.staticflickr.com/6053/6312937936_cebaf2feb9.jpg',
    'http://farm1.staticflickr.com/54/131841577_0e67642c02.jpg',
    'http://farm3.staticflickr.com/2293/2266151759_058e732577.jpg'
]

max_thread = 2 # 最大スレッド数
ary_threads = []
locker = Mutex::new
# max_threadで指定した数だけスレッドを開始
max_thread.times do |i|
  ary_threads << Thread.start { # スレッドを作成
    loop do
      url = locker.synchronize { urls.pop } # urlをひとつ取り出す。競合回避のためにsynchronizeで囲う
      break unless url # urlがなくなればループを終了
      puts "start download: #{url}"
      img = open(url) rescue next
      img.close
      file_path = "./#{Digest::MD5.hexdigest(url)}.jpg"
      File.rename(img.path, file_path)
      puts "end download: #{url}"
    end
  }
end

ary_threads.each { |th| th.join }
puts "finish process"

* Ruby - スレッドで並列処理! - mk-mode BLOG

Rubyでマルチスレッドプログラミング | TechRacho

逆引きRuby - スレッド』のスレッドの項目を参照

補足: ActiveRecordを複数スレッド環境で利用する

並行処理はカナリ奥が深そうです。こちらは、Parallelではなく、Rubyのスレッド処理を使って、ActiveRecordを複数スレッド環境で利用する方法です。

ActiveRecordを複数スレッド環境で利用する

補足: each を遅延評価しながら複数スレッドで並行処理

こちらはeachを遅延評価しつつ、複数スレッドで並行処理する方法です。1000回ごとに、APIを呼び出すといった場合に使える方法です。こちらもParallelではなく、Rubyのスレッド処理を使っています。

each を遅延評価しながら複数スレッドで並行処理

補足: DEPRECATION WARNING: Database.. エラー

並行処理を組み込んでいる途中で遭遇したWARNINGです。

DEPRECATION WARNING: Database connections will not be closed automatically, please close your database connection at the end of the thread by calling close on your connection. For example: ActiveRecord::Base.connection.close'

理由は、『Thread処理を行う場合はActiveRecordのコネクションを自分で閉じる』必要が有るためだそうです。ということで、閉じる際の処理です。

1
2
3
4
5
6
7
8
9
10
Thread.new {
  begin
    # 何かの処理
  rescue => ex
    # エラー処理
  ensure
    # ActiveRecordのコネクションを閉じる処理
    ActiveRecord::Base.connection.close
  end
}

ActiveRecordは、Threadを分けたら自分でコネクションを閉じないと駄目だった - バトルプログラマー@すま Blog

Special Thanks

Ruby で並列実行処理を簡単に書く - #生存戦略 、それは - subtech

画像処理 並列処理 parallel - peroon’s diary

橋本商会 » いかにしておっぱい画像をダウンロードするか〜2012 をRubyで書いた

とあるサイトから画像をダウンロードする

変更来歴

03/17 23:00 補足を全面リニュアール
03/26 23:45 Ruby自体のマルチスレッド機能のサンプルソースを修正
04/04 09:25 Queueによるマルチスレッドのサンプルソースを追加
05/06 17:20 Queueのサンプルソースを修正

おすすめの書籍