in_threads 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YzcyOTliODQ1ZDMyYmY1NDRiMTEyZTc4ODI0ZGViNTQ3MDJmODNhMQ==
5
- data.tar.gz: !binary |-
6
- ZWNlZTE1ZjE5NmNlYzdiOWM4Y2YxNjJiMDVlYjVmMjQ2ZGY1OTM0MQ==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MDJkYjFlZDRiMTJiZjc2OTU0NWM2NGJjMWU2MjQ3YzdkZDQ3OTkxZTY0N2Uw
10
- OWRjMTc4ODFjZTM5MTM1OGUxNDlkMTMxZGI2OTljZTRmZjAwYTZjY2I3OTMw
11
- ZDU0YTFhODA2YzFiOWNiZDlmNzBkYmQwZWU3MWJlOWRjYmI4MmU=
12
- data.tar.gz: !binary |-
13
- MDgzOWFkYmIwZjEzZDVlYjcyYTZlMjU4YzQwZTYyYThmNWM0MzExMjk4ZTY5
14
- NWIwOTlhMTA3Y2UyMjA0NzYxYTA5NTcyZWVlNmU1M2Q2M2E1ODRmNWU2OGU2
15
- ZjBlMzI0MTI2MWMzNTA3OWM2MDdkM2MxZWZlOGE3ZDc2NGQyNTU=
2
+ SHA1:
3
+ metadata.gz: 55a72b765291c267b859530ea8413d70201e45fc
4
+ data.tar.gz: 2227981ac7e0ed5c3e4b3a438a6087acfe427504
5
+ SHA512:
6
+ metadata.gz: 6eb1d9590ba97d801053e963772bdf69411e7b08c44c11c31f149ca71d2b0919b64bf07c7c73bda2748e17a99faa9abb3853bea7b161013c84c9b81336d466ba
7
+ data.tar.gz: dd1fa2f4e0d304a9f4f6a0ea33379fad3a28937f8d9e008e40e2e954a8d36ce613f3102d0259fd69154b4016045ac9cfed140f006235d5166d6b329bcc892e98
data/.rubocop.yml ADDED
@@ -0,0 +1,53 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
4
+
5
+ Lint/EndAlignment:
6
+ AlignWith: variable
7
+
8
+ Metrics/ClassLength:
9
+ Max: 150
10
+
11
+ Metrics/MethodLength:
12
+ Max: 25
13
+
14
+ Style/AccessModifierIndentation:
15
+ EnforcedStyle: outdent
16
+
17
+ Style/CaseIndentation:
18
+ IndentWhenRelativeTo: end
19
+
20
+ Style/DotPosition:
21
+ EnforcedStyle: trailing
22
+
23
+ Style/DoubleNegation:
24
+ Enabled: false
25
+
26
+ Style/Encoding:
27
+ EnforcedStyle: when_needed
28
+
29
+ Style/HashSyntax:
30
+ EnforcedStyle: hash_rockets
31
+
32
+ Style/IfUnlessModifier:
33
+ MaxLineLength: 40
34
+
35
+ Style/IndentHash:
36
+ EnforcedStyle: consistent
37
+
38
+ Style/PercentLiteralDelimiters:
39
+ PreferredDelimiters:
40
+ '%w': '[]'
41
+ '%W': '[]'
42
+
43
+ Style/Semicolon:
44
+ AllowAsExpressionSeparator: true
45
+
46
+ Style/SpaceBeforeBlockBraces:
47
+ EnforcedStyle: no_space
48
+
49
+ Style/SpaceInsideHashLiteralBraces:
50
+ EnforcedStyle: no_space
51
+
52
+ Style/TrailingComma:
53
+ EnforcedStyleForMultiline: comma
data/.travis.yml CHANGED
@@ -4,7 +4,17 @@ rvm:
4
4
  - 1.9.2
5
5
  - 1.9.3
6
6
  - 2.0.0
7
+ - 2.1.0
8
+ - ruby-head
7
9
  - jruby-18mode
8
10
  - jruby-19mode
11
+ - jruby-head
9
12
  - ree
10
- script: "bundle exec rspec"
13
+ script:
14
+ - bundle exec rspec
15
+ - '! bundle show rubocop || bundle exec rubocop' # run rubocop only if bundled
16
+ matrix:
17
+ fast_finish: true
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: jruby-head
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/README.markdown CHANGED
@@ -1,9 +1,13 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/in_threads.svg?style=flat)](https://rubygems.org/gems/in_threads)
2
+ [![Build Status](https://img.shields.io/travis/toy/in_threads/master.svg?style=flat)](https://travis-ci.org/toy/in_threads)
3
+ [![Code Climate](https://img.shields.io/codeclimate/github/toy/in_threads.svg?style=flat)](https://codeclimate.com/github/toy/in_threads)
4
+ [![Dependency Status](https://img.shields.io/gemnasium/toy/in_threads.svg?style=flat)](https://gemnasium.com/toy/in_threads)
5
+ [![Inch CI](http://inch-ci.org/github/toy/in_threads.svg?branch=master&style=flat)](http://inch-ci.org/github/toy/in_threads)
6
+
1
7
  # in_threads
2
8
 
3
9
  Easily execute ruby code in parallel.
4
10
 
5
- [![Build Status](https://travis-ci.org/toy/in_threads.png?branch=master)](https://travis-ci.org/toy/in_threads)
6
-
7
11
  ## Installation
8
12
 
9
13
  gem install in_threads
data/in_threads.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'in_threads'
5
- s.version = '1.2.2'
5
+ s.version = '1.3.0'
6
6
  s.summary = %q{Execute ruby code in parallel}
7
7
  s.homepage = "http://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
@@ -16,4 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.require_paths = %w[lib]
17
17
 
18
18
  s.add_development_dependency 'rspec', '~> 3.0'
19
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
20
+ s.add_development_dependency 'rubocop', '~> 0.26.0'
21
+ end
19
22
  end
data/lib/in_threads.rb CHANGED
@@ -1,16 +1,135 @@
1
1
  require 'thread'
2
+ require 'thwait'
2
3
  require 'delegate'
3
4
 
4
- class InThreads < Delegator
5
+ Enumerable.class_eval do
6
+ # Run enumerable method blocks in threads
7
+ #
8
+ # urls.in_threads.map do |url|
9
+ # url.fetch
10
+ # end
11
+ #
12
+ # Specify number of threads to use:
13
+ #
14
+ # files.in_threads(4).all? do |file|
15
+ # file.valid?
16
+ # end
17
+ #
18
+ # Passing block runs it against `each`
19
+ #
20
+ # urls.in_threads.each{ ... }
21
+ #
22
+ # is same as
23
+ #
24
+ # urls.in_threads{ ... }
25
+ def in_threads(thread_count = 10, &block)
26
+ InThreads.new(self, thread_count, &block)
27
+ end
28
+ end
29
+
30
+ # Run Enumerable methods with blocks in threads
31
+ class InThreads < SimpleDelegator
32
+ protected :__getobj__, :__setobj__
33
+
34
+ # Use ThreadsWait to limit number of threads
35
+ class ThreadLimiter
36
+ # Initialize with limit
37
+ def initialize(count)
38
+ @count = count
39
+ @waiter = ThreadsWait.new
40
+ end
41
+
42
+ # Without block behaves as `new`
43
+ # With block yields it with `self` and ensures running of `finalize`
44
+ def self.limit(count, &block)
45
+ limiter = new(count)
46
+ if block
47
+ begin
48
+ yield limiter
49
+ ensure
50
+ limiter.finalize
51
+ end
52
+ else
53
+ limiter
54
+ end
55
+ end
56
+
57
+ # Add thread to `ThreadsWait`, wait for finishing of one thread if limit
58
+ # reached
59
+ def <<(thread)
60
+ if @waiter.threads.length + 1 >= @count
61
+ @waiter.join(thread).join
62
+ else
63
+ @waiter.join_nowait(thread)
64
+ end
65
+ end
66
+
67
+ # Wait for waiting threads
68
+ def finalize
69
+ @waiter.all_waits(&:join)
70
+ end
71
+ end
72
+
73
+ # Yield objects of one enum in multiple places
74
+ class Splitter
75
+ # Enumerable using Queue
76
+ class Transfer
77
+ # Holds one object, for distinguishing eof
78
+ class Item
79
+ attr_reader :value
80
+
81
+ def initialize(value)
82
+ @value = value
83
+ end
84
+ end
85
+
86
+ include Enumerable
87
+
88
+ def initialize
89
+ @queue = Queue.new
90
+ end
91
+
92
+ def <<(object)
93
+ @queue << Item.new(object)
94
+ end
95
+
96
+ def finish
97
+ @queue << nil
98
+ end
99
+
100
+ def each
101
+ while (o = @queue.pop)
102
+ yield o.value
103
+ end
104
+ nil # non reusable
105
+ end
106
+ end
107
+
108
+ # Enums receiving items
109
+ attr_reader :enums
110
+
111
+ def initialize(enumerable, enum_count)
112
+ @enums = Array.new(enum_count){ Transfer.new }
113
+ @filler = Thread.new do
114
+ enumerable.each do |o|
115
+ @enums.each do |enum|
116
+ enum << o
117
+ end
118
+ end
119
+ @enums.each(&:finish)
120
+ end
121
+ end
122
+ end
123
+
5
124
  attr_reader :enumerable, :thread_count
6
125
  def initialize(enumerable, thread_count = 10, &block)
7
126
  super(enumerable)
8
127
  @enumerable, @thread_count = enumerable, thread_count.to_i
9
128
  unless enumerable.is_a?(Enumerable)
10
- raise ArgumentError.new('`enumerable` should include Enumerable.')
129
+ fail ArgumentError, '`enumerable` should include Enumerable.'
11
130
  end
12
131
  if thread_count < 2
13
- raise ArgumentError.new('`thread_count` can\'t be less than 2.')
132
+ fail ArgumentError, '`thread_count` can\'t be less than 2.'
14
133
  end
15
134
  each(&block) if block
16
135
  end
@@ -25,21 +144,21 @@ class InThreads < Delegator
25
144
  #
26
145
  # use :run_in_threads_consecutive, :for => %w[all? any? none? one?]
27
146
  #
28
- # <tt>:for</tt> is required
29
- # <tt>:ignore_undefined</tt> ignores methods which are not present in <tt>Enumerable.instance_methods</tt>
147
+ # `:for` is required
148
+ # `:ignore_undefined` ignores methods which are not present in
149
+ # `Enumerable.instance_methods`
30
150
  def use(runner, options)
31
151
  methods = Array(options[:for])
32
- raise 'no methods provided using :for option' if methods.empty?
152
+ fail 'no methods provided using :for option' if methods.empty?
33
153
  ignore_undefined = options[:ignore_undefined]
34
154
  enumerable_methods = Enumerable.instance_methods.map(&:to_s)
35
155
  methods.each do |method|
36
- unless ignore_undefined && !enumerable_methods.include?(method)
37
- class_eval <<-RUBY
38
- def #{method}(*args, &block)
39
- #{runner}(enumerable, :#{method}, *args, &block)
40
- end
41
- RUBY
42
- end
156
+ next if ignore_undefined && !enumerable_methods.include?(method)
157
+ class_eval <<-RUBY
158
+ def #{method}(*args, &block)
159
+ #{runner}(:#{method}, *args, &block)
160
+ end
161
+ RUBY
43
162
  end
44
163
  end
45
164
  end
@@ -71,10 +190,11 @@ class InThreads < Delegator
71
190
  chunk slice_before
72
191
  ], :ignore_undefined => true
73
192
 
74
- # Special case method, works by applying <tt>run_in_threads_consecutive</tt> with map on enumerable returned by blockless run
193
+ # Special case method, works by applying `run_in_threads_consecutive` with
194
+ # map on enumerable returned by blockless run
75
195
  def grep(*args, &block)
76
196
  if block
77
- run_in_threads_consecutive(enumerable.grep(*args), :map, &block)
197
+ self.class.new(enumerable.grep(*args), thread_count).map(&block)
78
198
  else
79
199
  enumerable.grep(*args)
80
200
  end
@@ -87,16 +207,8 @@ class InThreads < Delegator
87
207
 
88
208
  protected
89
209
 
90
- def __getobj__
91
- @enumerable
92
- end
93
-
94
- def __setobj__(obj)
95
- @enumerable = obj
96
- end
97
-
98
210
  # Use for methods which don't use block result
99
- def run_in_threads_return_original_enum(enumerable, method, *args, &block)
211
+ def run_in_threads_return_original_enum(method, *args, &block)
100
212
  if block
101
213
  ThreadLimiter.limit(thread_count) do |limiter|
102
214
  enumerable.send(method, *args) do |*block_args|
@@ -108,24 +220,25 @@ protected
108
220
  end
109
221
  end
110
222
 
111
- # Use for methods which do use block result and fire objects in same way as <tt>each</tt>
112
- def run_in_threads_consecutive(enumerable, method, *args, &block)
223
+ # Use for methods which do use block result
224
+ def run_in_threads_consecutive(method, *args, &block)
113
225
  if block
114
- begin
115
- enum_a, enum_b = Filler.new(enumerable, 2).extractors
116
- results = Queue.new
117
- runner = Thread.new do
118
- Thread.current.priority = -1
119
- ThreadLimiter.limit(thread_count) do |limiter|
120
- enum_a.each do |object|
121
- break if Thread.current[:stop]
122
- thread = Thread.new(object, &block)
123
- results << thread
124
- limiter << thread
125
- end
226
+ enum_a, enum_b = Splitter.new(enumerable, 2).enums
227
+ results = Queue.new
228
+ runner = Thread.new do
229
+ Thread.current.priority = -1
230
+ ThreadLimiter.limit(thread_count) do |limiter|
231
+ enum_a.each do |object|
232
+ break if Thread.current[:stop]
233
+ thread = Thread.new(object, &block)
234
+ results << thread
235
+ limiter << thread
126
236
  end
127
237
  end
128
- enum_b.send(method, *args) do |object|
238
+ end
239
+
240
+ begin
241
+ enum_b.send(method, *args) do |_object|
129
242
  results.pop.value
130
243
  end
131
244
  ensure
@@ -138,11 +251,7 @@ protected
138
251
  end
139
252
 
140
253
  # Use for methods which don't use blocks or can not use threads
141
- def run_without_threads(enumerable, method, *args, &block)
254
+ def run_without_threads(method, *args, &block)
142
255
  enumerable.send(method, *args, &block)
143
256
  end
144
257
  end
145
-
146
- require 'in_threads/enumerable'
147
- require 'in_threads/filler'
148
- require 'in_threads/thread_limiter'