in_threads 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +6 -14
- data/.rubocop.yml +53 -0
- data/.travis.yml +11 -1
- data/Gemfile +1 -1
- data/README.markdown +6 -2
- data/in_threads.gemspec +4 -1
- data/lib/in_threads.rb +153 -44
- data/spec/in_threads_spec.rb +277 -202
- data/spec/spec_helper.rb +0 -0
- metadata +22 -9
- data/lib/in_threads/enumerable.rb +0 -26
- data/lib/in_threads/filler.rb +0 -54
- data/lib/in_threads/thread_limiter.rb +0 -41
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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:
|
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
data/README.markdown
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
[](https://rubygems.org/gems/in_threads)
|
2
|
+
[](https://travis-ci.org/toy/in_threads)
|
3
|
+
[](https://codeclimate.com/github/toy/in_threads)
|
4
|
+
[](https://gemnasium.com/toy/in_threads)
|
5
|
+
[](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
|
-
[](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.
|
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
|
-
|
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
|
-
|
129
|
+
fail ArgumentError, '`enumerable` should include Enumerable.'
|
11
130
|
end
|
12
131
|
if thread_count < 2
|
13
|
-
|
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
|
-
#
|
29
|
-
#
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
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
|
-
|
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(
|
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
|
112
|
-
def run_in_threads_consecutive(
|
223
|
+
# Use for methods which do use block result
|
224
|
+
def run_in_threads_consecutive(method, *args, &block)
|
113
225
|
if block
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
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(
|
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'
|