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 +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
|
+
[![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.
|
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'
|