in_threads 1.1.0 → 1.1.1
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.
- data/in_threads.gemspec +1 -1
- data/lib/in_threads.rb +23 -64
- data/lib/in_threads/filler.rb +55 -0
- data/lib/in_threads/thread_limiter.rb +41 -0
- data/spec/in_threads_spec.rb +327 -297
- metadata +7 -5
data/in_threads.gemspec
CHANGED
data/lib/in_threads.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'thwait'
|
3
2
|
|
4
3
|
module Enumerable
|
5
4
|
# Run enumerable method blocks in threads
|
@@ -26,8 +25,6 @@ module Enumerable
|
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
29
|
-
# TODO: all ruby1.9.3 methods
|
30
|
-
|
31
28
|
class InThreads
|
32
29
|
(
|
33
30
|
instance_methods.map(&:to_s) -
|
@@ -53,22 +50,17 @@ class InThreads
|
|
53
50
|
end
|
54
51
|
|
55
52
|
class << self
|
56
|
-
# List of instance_methods of Enumerable
|
57
|
-
def enumerable_methods
|
58
|
-
Enumerable.instance_methods.map(&:to_s)
|
59
|
-
end
|
60
|
-
|
61
53
|
# Specify runner to use
|
62
54
|
#
|
63
55
|
# use :run_in_threads_consecutive, :for => %w[all? any? none? one?]
|
64
56
|
#
|
65
57
|
# <tt>:for</tt> is required
|
66
|
-
# <tt>:ignore_undefined</tt> ignores methods which are not present in
|
58
|
+
# <tt>:ignore_undefined</tt> ignores methods which are not present in <tt>Enumerable.instance_methods</tt>
|
67
59
|
def use(runner, options)
|
68
60
|
methods = Array(options[:for])
|
69
61
|
raise 'no methods provided using :for option' if methods.empty?
|
70
62
|
ignore_undefined = options[:ignore_undefined]
|
71
|
-
enumerable_methods =
|
63
|
+
enumerable_methods = Enumerable.instance_methods.map(&:to_s)
|
72
64
|
methods.each do |method|
|
73
65
|
unless ignore_undefined && !enumerable_methods.include?(method)
|
74
66
|
class_eval <<-RUBY
|
@@ -81,15 +73,8 @@ class InThreads
|
|
81
73
|
end
|
82
74
|
end
|
83
75
|
|
84
|
-
use :
|
85
|
-
use :
|
86
|
-
all? any? none? one?
|
87
|
-
detect find find_index drop_while take_while
|
88
|
-
partition find_all select reject count
|
89
|
-
collect map group_by max_by min_by minmax_by sort_by
|
90
|
-
flat_map collect_concat
|
91
|
-
], :ignore_undefined => true
|
92
|
-
use :run_in_threads_block_result_irrelevant, :for => %w[
|
76
|
+
use :run_in_threads_return_original_enum, :for => %w[each]
|
77
|
+
use :run_in_threads_return_original_enum, :for => %w[
|
93
78
|
reverse_each
|
94
79
|
each_with_index enum_with_index
|
95
80
|
each_cons each_slice enum_cons enum_slice
|
@@ -97,6 +82,13 @@ class InThreads
|
|
97
82
|
cycle
|
98
83
|
each_entry
|
99
84
|
], :ignore_undefined => true
|
85
|
+
use :run_in_threads_consecutive, :for => %w[
|
86
|
+
all? any? none? one?
|
87
|
+
detect find find_index drop_while take_while
|
88
|
+
partition find_all select reject count
|
89
|
+
collect map group_by max_by min_by minmax_by sort_by
|
90
|
+
flat_map collect_concat
|
91
|
+
], :ignore_undefined => true
|
100
92
|
use :run_without_threads, :for => %w[
|
101
93
|
inject reduce
|
102
94
|
max min minmax sort
|
@@ -119,50 +111,15 @@ class InThreads
|
|
119
111
|
|
120
112
|
protected
|
121
113
|
|
122
|
-
|
123
|
-
|
124
|
-
# Initialize with limit
|
125
|
-
def initialize(count)
|
126
|
-
@count = count
|
127
|
-
@waiter = ThreadsWait.new
|
128
|
-
end
|
129
|
-
|
130
|
-
# Without block behaves as <tt>new</tt>
|
131
|
-
# With block yields it with <tt>self</tt> and ensures running of <tt>finalize</tt>
|
132
|
-
def self.limit(count, &block)
|
133
|
-
limiter = new(count)
|
134
|
-
if block
|
135
|
-
begin
|
136
|
-
yield limiter
|
137
|
-
ensure
|
138
|
-
limiter.finalize
|
139
|
-
end
|
140
|
-
else
|
141
|
-
limiter
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Add thread to <tt>ThreadsWait</tt>, wait for finishing of one thread if limit reached
|
146
|
-
def add(thread)
|
147
|
-
if @waiter.threads.length + 1 >= @count
|
148
|
-
@waiter.join(thread)
|
149
|
-
else
|
150
|
-
@waiter.join_nowait(thread)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Wait for waiting threads
|
155
|
-
def finalize
|
156
|
-
@waiter.all_waits
|
157
|
-
end
|
158
|
-
end
|
114
|
+
autoload :ThreadLimiter, 'in_threads/thread_limiter'
|
115
|
+
autoload :Filler, 'in_threads/filler'
|
159
116
|
|
160
117
|
# Use for methods which don't use block result
|
161
|
-
def
|
118
|
+
def run_in_threads_return_original_enum(enumerable, method, *args, &block)
|
162
119
|
if block
|
163
120
|
ThreadLimiter.limit(thread_count) do |limiter|
|
164
121
|
enumerable.send(method, *args) do |*block_args|
|
165
|
-
limiter
|
122
|
+
limiter << Thread.new(*block_args, &block)
|
166
123
|
end
|
167
124
|
end
|
168
125
|
else
|
@@ -174,19 +131,21 @@ protected
|
|
174
131
|
def run_in_threads_consecutive(enumerable, method, *args, &block)
|
175
132
|
if block
|
176
133
|
begin
|
177
|
-
|
134
|
+
enum_a, enum_b = Filler.new(enumerable, 2).extractors
|
135
|
+
results = Queue.new
|
178
136
|
runner = Thread.new do
|
137
|
+
Thread.current.priority = -1
|
179
138
|
ThreadLimiter.limit(thread_count) do |limiter|
|
180
|
-
|
139
|
+
enum_a.each do |object|
|
181
140
|
break if Thread.current[:stop]
|
182
141
|
thread = Thread.new(object, &block)
|
183
|
-
|
184
|
-
limiter
|
142
|
+
results << thread
|
143
|
+
limiter << thread
|
185
144
|
end
|
186
145
|
end
|
187
146
|
end
|
188
|
-
|
189
|
-
|
147
|
+
enum_b.send(method, *args) do |object|
|
148
|
+
results.pop.value
|
190
149
|
end
|
191
150
|
ensure
|
192
151
|
runner[:stop] = true
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class InThreads
|
4
|
+
class Filler
|
5
|
+
class Extractor
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(filler)
|
9
|
+
@filler = filler
|
10
|
+
@queue = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(o)
|
14
|
+
@queue.push(o)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
begin
|
19
|
+
loop do
|
20
|
+
while @filler.synchronize{ @queue.empty? }
|
21
|
+
@filler.run
|
22
|
+
end
|
23
|
+
yield @filler.synchronize{ @queue.shift }
|
24
|
+
end
|
25
|
+
rescue ThreadError => e
|
26
|
+
end
|
27
|
+
nil # non reusable
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :extractors
|
32
|
+
def initialize(enum, extractor_count)
|
33
|
+
@extractors = Array.new(extractor_count){ Extractor.new(self) }
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@filler = Thread.new do
|
36
|
+
enum.each do |o|
|
37
|
+
Thread.stop
|
38
|
+
synchronize do
|
39
|
+
@extractors.each do |extractor|
|
40
|
+
extractor.push(o)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run
|
48
|
+
@filler.run
|
49
|
+
end
|
50
|
+
|
51
|
+
def synchronize(&block)
|
52
|
+
@mutex.synchronize(&block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'thwait'
|
2
|
+
|
3
|
+
class InThreads
|
4
|
+
# Use ThreadsWait to limit number of threads
|
5
|
+
class ThreadLimiter
|
6
|
+
# Initialize with limit
|
7
|
+
def initialize(count)
|
8
|
+
@count = count
|
9
|
+
@waiter = ThreadsWait.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Without block behaves as <tt>new</tt>
|
13
|
+
# With block yields it with <tt>self</tt> and ensures running of <tt>finalize</tt>
|
14
|
+
def self.limit(count, &block)
|
15
|
+
limiter = new(count)
|
16
|
+
if block
|
17
|
+
begin
|
18
|
+
yield limiter
|
19
|
+
ensure
|
20
|
+
limiter.finalize
|
21
|
+
end
|
22
|
+
else
|
23
|
+
limiter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add thread to <tt>ThreadsWait</tt>, wait for finishing of one thread if limit reached
|
28
|
+
def <<(thread)
|
29
|
+
if @waiter.threads.length + 1 >= @count
|
30
|
+
@waiter.join(thread)
|
31
|
+
else
|
32
|
+
@waiter.join_nowait(thread)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Wait for waiting threads
|
37
|
+
def finalize
|
38
|
+
@waiter.all_waits
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/in_threads_spec.rb
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
|
3
3
|
class Item
|
4
|
+
attr_reader :rand
|
4
5
|
def initialize(i)
|
5
|
-
@i
|
6
|
+
@i = i
|
7
|
+
@rand = Kernel.rand
|
8
|
+
@sleep = Kernel.rand
|
6
9
|
end
|
7
10
|
|
8
11
|
class MiddleMatcher
|
9
12
|
def ===(item)
|
10
13
|
raise "#{item.inspect} is not an Item" unless item.is_a?(Item)
|
11
|
-
(0.25..0.75) === item.
|
14
|
+
(0.25..0.75) === item.rand
|
12
15
|
end
|
13
16
|
end
|
14
17
|
|
15
|
-
def work
|
16
|
-
sleep @rand * 0.008
|
17
|
-
end
|
18
|
-
|
19
18
|
def value
|
20
|
-
|
19
|
+
sleep; rand
|
21
20
|
end
|
22
21
|
|
23
22
|
def check?
|
@@ -31,6 +30,12 @@ class Item
|
|
31
30
|
def touch_n_check?(*args)
|
32
31
|
touch(*args); check?
|
33
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def sleep
|
37
|
+
Kernel.sleep @sleep * 0.01
|
38
|
+
end
|
34
39
|
end
|
35
40
|
|
36
41
|
class ValueItem < Item
|
@@ -40,7 +45,7 @@ class ValueItem < Item
|
|
40
45
|
end
|
41
46
|
|
42
47
|
def value
|
43
|
-
|
48
|
+
sleep; @value
|
44
49
|
end
|
45
50
|
|
46
51
|
def check?
|
@@ -70,419 +75,444 @@ describe "in_threads" do
|
|
70
75
|
Time.now - start
|
71
76
|
end
|
72
77
|
|
73
|
-
|
74
|
-
|
75
|
-
|
78
|
+
describe "consistency" do
|
79
|
+
describe "verifying params" do
|
80
|
+
it "should complain about using with non enumerable" do
|
81
|
+
proc{ InThreads.new(1) }.should raise_error(ArgumentError)
|
82
|
+
end
|
76
83
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
84
|
+
[1..10, 10.times, {}, []].each do |o|
|
85
|
+
it "should complain about using with #{o.class}" do
|
86
|
+
proc{ InThreads.new(o) }.should_not raise_error
|
87
|
+
end
|
88
|
+
end
|
81
89
|
|
82
|
-
|
83
|
-
|
84
|
-
proc{ InThreads.new(o) }.should_not raise_error
|
90
|
+
it "should complain about using less than 2 threads" do
|
91
|
+
proc{ 10.times.in_threads(1) }.should raise_error(ArgumentError)
|
85
92
|
end
|
86
|
-
end
|
87
93
|
|
88
|
-
|
89
|
-
|
94
|
+
it "should not complain about using 2 or more threads" do
|
95
|
+
proc{ 10.times.in_threads(2) }.should_not raise_error
|
96
|
+
end
|
90
97
|
end
|
91
98
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
99
|
+
describe "in_threads" do
|
100
|
+
it "should not change existing instance" do
|
101
|
+
threaded = enum.in_threads(10)
|
102
|
+
proc{ threaded.in_threads(20) }.should_not change(threaded, :thread_count)
|
103
|
+
end
|
96
104
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
105
|
+
it "should create new instance with different title when called on WithProgress" do
|
106
|
+
threaded = enum.in_threads(10)
|
107
|
+
tthreaded = threaded.in_threads(20)
|
108
|
+
threaded.thread_count.should == 10
|
109
|
+
tthreaded.thread_count.should == 20
|
110
|
+
tthreaded.class.should == threaded.class
|
111
|
+
tthreaded.object_id.should_not == threaded.object_id
|
112
|
+
tthreaded.enumerable.should == threaded.enumerable
|
113
|
+
end
|
101
114
|
end
|
102
115
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
116
|
+
describe "thread count" do
|
117
|
+
let(:enum){ 100.times.map{ |i| ValueItem.new(i, i < 50) } }
|
118
|
+
|
119
|
+
%w[each map all?].each do |method|
|
120
|
+
it "should run in specified number of threads for #{method}" do
|
121
|
+
@thread_count = 0
|
122
|
+
@max_thread_count = 0
|
123
|
+
@mutex = Mutex.new
|
124
|
+
enum.in_threads(13).send(method) do |o|
|
125
|
+
@mutex.synchronize do
|
126
|
+
@thread_count += 1
|
127
|
+
@max_thread_count = [@max_thread_count, @thread_count].max
|
128
|
+
end
|
129
|
+
res = o.check?
|
130
|
+
@mutex.synchronize do
|
131
|
+
@thread_count -= 1
|
132
|
+
end
|
133
|
+
res
|
134
|
+
end
|
135
|
+
@thread_count.should == 0
|
136
|
+
@max_thread_count.should == 13
|
137
|
+
end
|
138
|
+
end
|
111
139
|
end
|
112
|
-
end
|
113
140
|
|
114
|
-
|
115
|
-
|
141
|
+
describe "underlying enumerable usage" do
|
142
|
+
class CheckEachCalls
|
143
|
+
include Enumerable
|
116
144
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@mutex = Mutex.new
|
122
|
-
enum.in_threads(13).send(method) do |o|
|
123
|
-
@mutex.synchronize do
|
124
|
-
@thread_count += 1
|
125
|
-
@max_thread_count = [@max_thread_count, @thread_count].max
|
126
|
-
end
|
127
|
-
res = o.check?
|
128
|
-
@mutex.synchronize do
|
129
|
-
@thread_count -= 1
|
145
|
+
def each
|
146
|
+
each_started
|
147
|
+
100.times.each do |i|
|
148
|
+
yield ValueItem.new(i, i < 50)
|
130
149
|
end
|
131
|
-
res
|
132
150
|
end
|
133
|
-
|
134
|
-
|
151
|
+
end
|
152
|
+
let(:enum){ CheckEachCalls.new }
|
153
|
+
|
154
|
+
%w[each map all?].each do |method|
|
155
|
+
it "should call underlying enumerable.each only once for #{method}" do
|
156
|
+
enum.should_receive(:each_started).once
|
157
|
+
enum.in_threads(13).send(method, &:check?)
|
158
|
+
end
|
135
159
|
end
|
136
160
|
end
|
137
161
|
end
|
138
162
|
|
139
|
-
describe "
|
140
|
-
|
141
|
-
|
163
|
+
describe "methods" do
|
164
|
+
(Enumerable.instance_methods - 10.times.in_threads.class.instance_methods).each do |method|
|
165
|
+
pending method
|
142
166
|
end
|
143
167
|
|
144
|
-
|
145
|
-
enum
|
146
|
-
|
147
|
-
|
168
|
+
describe "each" do
|
169
|
+
it "should return same enum after running" do
|
170
|
+
enum.in_threads.each(&:value).should == enum
|
171
|
+
end
|
148
172
|
|
149
|
-
|
150
|
-
|
151
|
-
|
173
|
+
it "should execute block for each element" do
|
174
|
+
enum.each{ |o| o.should_receive(:touch).once }
|
175
|
+
enum.in_threads.each(&:touch_n_value)
|
176
|
+
end
|
152
177
|
|
153
|
-
|
154
|
-
|
155
|
-
|
178
|
+
it "should run faster with threads" do
|
179
|
+
measure{ enum.in_threads.each(&:value) }.should < measure{ enum.each(&:value) } * speed_coef
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should run faster with more threads" do
|
183
|
+
measure{ enum.in_threads(10).each(&:value) }.should < measure{ enum.in_threads(2).each(&:value) } * speed_coef
|
184
|
+
end
|
156
185
|
|
157
|
-
|
158
|
-
|
186
|
+
it "should return same enum without block" do
|
187
|
+
enum.in_threads.each.to_a.should == enum.each.to_a
|
188
|
+
end
|
159
189
|
end
|
160
|
-
end
|
161
190
|
|
162
|
-
|
163
|
-
|
164
|
-
|
191
|
+
%w[each_with_index enum_with_index].each do |method|
|
192
|
+
describe_enum_method method do
|
193
|
+
let(:runner){ proc{ |o, i| o.value } }
|
194
|
+
|
195
|
+
it "should return same result with threads" do
|
196
|
+
enum.in_threads.send(method, &runner).should == enum.send(method, &runner)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should fire same objects" do
|
200
|
+
enum.send(method){ |o, i| o.should_receive(:touch).with(i).once }
|
201
|
+
enum.in_threads.send(method){ |o, i| o.touch_n_value(i) }
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should run faster with threads" do
|
205
|
+
measure{ enum.in_threads.send(method, &runner) }.should < measure{ enum.send(method, &runner) } * speed_coef
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should return same enum without block" do
|
209
|
+
enum.in_threads.send(method).to_a.should == enum.send(method).to_a
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
165
213
|
|
214
|
+
describe "reverse_each" do
|
166
215
|
it "should return same result with threads" do
|
167
|
-
enum.in_threads.
|
216
|
+
enum.in_threads.reverse_each(&:value).should == enum.reverse_each(&:value)
|
168
217
|
end
|
169
218
|
|
170
|
-
it "should fire same objects" do
|
171
|
-
|
172
|
-
|
219
|
+
it "should fire same objects in reverse order" do
|
220
|
+
@order = mock('order', :notify => nil)
|
221
|
+
@order.should_receive(:notify).with(enum.last).ordered
|
222
|
+
@order.should_receive(:notify).with(enum[enum.length / 2]).ordered
|
223
|
+
@order.should_receive(:notify).with(enum.first).ordered
|
224
|
+
enum.reverse_each{ |o| o.should_receive(:touch).once }
|
225
|
+
@mutex = Mutex.new
|
226
|
+
enum.in_threads.reverse_each do |o|
|
227
|
+
@mutex.synchronize{ @order.notify(o) }
|
228
|
+
o.touch_n_value
|
229
|
+
end
|
173
230
|
end
|
174
231
|
|
175
232
|
it "should run faster with threads" do
|
176
|
-
measure{ enum.in_threads.
|
233
|
+
measure{ enum.in_threads.reverse_each(&:value) }.should < measure{ enum.reverse_each(&:value) } * speed_coef
|
177
234
|
end
|
178
235
|
|
179
236
|
it "should return same enum without block" do
|
180
|
-
enum.in_threads.
|
237
|
+
enum.in_threads.reverse_each.to_a.should == enum.reverse_each.to_a
|
181
238
|
end
|
182
239
|
end
|
183
|
-
end
|
184
240
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
241
|
+
%w[
|
242
|
+
all? any? none? one?
|
243
|
+
detect find find_index drop_while take_while
|
244
|
+
].each do |method|
|
245
|
+
describe method do
|
246
|
+
let(:enum){ 100.times.map{ |i| ValueItem.new(i, i % 2 == 1) } }
|
189
247
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
@order.should_receive(:notify).with(enum[enum.length / 2]).ordered
|
194
|
-
@order.should_receive(:notify).with(enum.first).ordered
|
195
|
-
enum.reverse_each{ |o| o.should_receive(:touch).once }
|
196
|
-
@mutex = Mutex.new
|
197
|
-
enum.in_threads.reverse_each do |o|
|
198
|
-
@mutex.synchronize{ @order.notify(o) }
|
199
|
-
o.touch_n_value
|
200
|
-
end
|
201
|
-
end
|
248
|
+
it "should return same result with threads" do
|
249
|
+
enum.in_threads.send(method, &:check?).should == enum.send(method, &:check?)
|
250
|
+
end
|
202
251
|
|
203
|
-
|
204
|
-
|
205
|
-
|
252
|
+
it "should fire same objects but not all" do
|
253
|
+
a = []
|
254
|
+
enum.send(method) do |o|
|
255
|
+
a << o
|
256
|
+
o.check?
|
257
|
+
end
|
206
258
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
end
|
259
|
+
@a = []
|
260
|
+
@mutex = Mutex.new
|
261
|
+
enum.in_threads.send(method){ |o| @mutex.synchronize{ @a << o }; o.check? }
|
211
262
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
].each do |method|
|
216
|
-
describe method do
|
217
|
-
let(:enum){ 100.times.map{ |i| ValueItem.new(i, i % 2 == 1) } }
|
263
|
+
@a.length.should >= a.length
|
264
|
+
@a.length.should <= enum.length * 0.5
|
265
|
+
end
|
218
266
|
|
219
|
-
|
220
|
-
|
267
|
+
it "should run faster with threads" do
|
268
|
+
boolean = %w[all? drop_while take_while].include?(method)
|
269
|
+
enum = 30.times.map{ |i| ValueItem.new(i, boolean) }
|
270
|
+
measure{ enum.in_threads.send(method, &:check?) }.should < measure{ enum.send(method, &:check?) } * speed_coef
|
271
|
+
end
|
221
272
|
end
|
273
|
+
end
|
222
274
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
o.check?
|
275
|
+
%w[partition find_all select reject count].each do |method|
|
276
|
+
describe method do
|
277
|
+
it "should return same result with threads" do
|
278
|
+
enum.in_threads.send(method, &:check?).should == enum.send(method, &:check?)
|
228
279
|
end
|
229
280
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
@a.length.should >= a.length
|
235
|
-
@a.length.should <= enum.length / 2
|
236
|
-
end
|
281
|
+
it "should fire same objects" do
|
282
|
+
enum.send(method){ |o| o.should_receive(:touch).once }
|
283
|
+
enum.in_threads.send(method, &:touch_n_check?)
|
284
|
+
end
|
237
285
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
measure{ enum.in_threads.send(method, &:check?) }.should < measure{ enum.send(method, &:check?) } * speed_coef
|
286
|
+
it "should run faster with threads" do
|
287
|
+
measure{ enum.in_threads.send(method, &:check?) }.should < measure{ enum.send(method, &:check?) } * speed_coef
|
288
|
+
end
|
242
289
|
end
|
243
290
|
end
|
244
|
-
end
|
245
291
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
292
|
+
%w[collect map group_by max_by min_by minmax_by sort_by].each do |method|
|
293
|
+
describe method do
|
294
|
+
it "should return same result with threads" do
|
295
|
+
enum.in_threads.send(method, &:value).should == enum.send(method, &:value)
|
296
|
+
end
|
251
297
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
298
|
+
it "should fire same objects" do
|
299
|
+
enum.send(method){ |o| o.should_receive(:touch).once; 0 }
|
300
|
+
enum.in_threads.send(method, &:touch_n_value)
|
301
|
+
end
|
256
302
|
|
257
|
-
|
258
|
-
|
303
|
+
it "should run faster with threads" do
|
304
|
+
measure{ enum.in_threads.send(method, &:value) }.should < measure{ enum.send(method, &:value) } * speed_coef
|
305
|
+
end
|
259
306
|
end
|
260
307
|
end
|
261
|
-
end
|
262
308
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
enum.in_threads.send(method, &:value).should == enum.send(method, &:value)
|
267
|
-
end
|
309
|
+
%w[each_cons each_slice enum_slice enum_cons].each do |method|
|
310
|
+
describe_enum_method method do
|
311
|
+
let(:runner){ proc{ |a| a.each(&:value) } }
|
268
312
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
313
|
+
it "should fire same objects" do
|
314
|
+
enum.send(method, 3){ |a| a.first.should_receive(:touch).with(a).once }
|
315
|
+
enum.in_threads.send(method, 3){ |a| a.first.touch_n_value(a) }
|
316
|
+
end
|
273
317
|
|
274
|
-
|
275
|
-
|
318
|
+
it "should return same with block" do
|
319
|
+
enum.in_threads.send(method, 3, &runner).should == enum.send(method, 3, &runner)
|
320
|
+
end
|
321
|
+
|
322
|
+
it "should run faster with threads" do
|
323
|
+
measure{ enum.in_threads.send(method, 3, &runner) }.should < measure{ enum.send(method, 3, &runner) } * speed_coef
|
324
|
+
end
|
325
|
+
|
326
|
+
it "should return same without block" do
|
327
|
+
enum.in_threads.send(method, 3).to_a.should == enum.send(method, 3).to_a
|
328
|
+
end
|
276
329
|
end
|
277
330
|
end
|
278
|
-
end
|
279
331
|
|
280
|
-
|
281
|
-
describe_enum_method method do
|
332
|
+
describe "zip" do
|
282
333
|
let(:runner){ proc{ |a| a.each(&:value) } }
|
283
334
|
|
284
335
|
it "should fire same objects" do
|
285
|
-
enum.
|
286
|
-
enum.in_threads.
|
336
|
+
enum.zip(enum, enum){ |a| a.first.should_receive(:touch).with(a).once }
|
337
|
+
enum.in_threads.zip(enum, enum){ |a| a.first.touch_n_value(a) }
|
287
338
|
end
|
288
339
|
|
289
340
|
it "should return same with block" do
|
290
|
-
enum.in_threads.
|
341
|
+
enum.in_threads.zip(enum, enum, &runner).should == enum.zip(enum, enum, &runner)
|
291
342
|
end
|
292
343
|
|
293
344
|
it "should run faster with threads" do
|
294
|
-
measure{ enum.in_threads.
|
345
|
+
measure{ enum.in_threads.zip(enum, enum, &runner) }.should < measure{ enum.zip(enum, enum, &runner) } * speed_coef
|
295
346
|
end
|
296
347
|
|
297
348
|
it "should return same without block" do
|
298
|
-
enum.in_threads.
|
349
|
+
enum.in_threads.zip(enum, enum).should == enum.zip(enum, enum)
|
299
350
|
end
|
300
351
|
end
|
301
|
-
end
|
302
|
-
|
303
|
-
describe "zip" do
|
304
|
-
let(:runner){ proc{ |a| a.each(&:value) } }
|
305
|
-
|
306
|
-
it "should fire same objects" do
|
307
|
-
enum.zip(enum, enum){ |a| a.first.should_receive(:touch).with(a).once }
|
308
|
-
enum.in_threads.zip(enum, enum){ |a| a.first.touch_n_value(a) }
|
309
|
-
end
|
310
|
-
|
311
|
-
it "should return same with block" do
|
312
|
-
enum.in_threads.zip(enum, enum, &runner).should == enum.zip(enum, enum, &runner)
|
313
|
-
end
|
314
|
-
|
315
|
-
it "should run faster with threads" do
|
316
|
-
measure{ enum.in_threads.zip(enum, enum, &runner) }.should < measure{ enum.zip(enum, enum, &runner) } * speed_coef
|
317
|
-
end
|
318
|
-
|
319
|
-
it "should return same without block" do
|
320
|
-
enum.in_threads.zip(enum, enum).should == enum.zip(enum, enum)
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
describe "cycle" do
|
325
|
-
it "should fire same objects" do
|
326
|
-
enum.cycle(1){ |o| o.should_receive(:touch).exactly(3).times }
|
327
|
-
enum.in_threads.cycle(3, &:touch_n_value)
|
328
|
-
end
|
329
|
-
|
330
|
-
it "should run faster with threads" do
|
331
|
-
measure{ enum.in_threads.cycle(3, &:work) }.should < measure{ enum.cycle(3, &:work) } * speed_coef
|
332
|
-
end
|
333
|
-
|
334
|
-
it "should return same enum without block" do
|
335
|
-
enum.in_threads.cycle(3).to_a.should == enum.cycle(3).to_a
|
336
|
-
end
|
337
|
-
end
|
338
352
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
enum.in_threads.grep(matcher, &:touch_n_value)
|
345
|
-
end
|
353
|
+
describe "cycle" do
|
354
|
+
it "should fire same objects" do
|
355
|
+
enum.cycle(1){ |o| o.should_receive(:touch).exactly(3).times }
|
356
|
+
enum.in_threads.cycle(3, &:touch_n_value)
|
357
|
+
end
|
346
358
|
|
347
|
-
|
348
|
-
|
349
|
-
|
359
|
+
it "should run faster with threads" do
|
360
|
+
measure{ enum.in_threads.cycle(3, &:value) }.should < measure{ enum.cycle(3, &:value) } * speed_coef
|
361
|
+
end
|
350
362
|
|
351
|
-
|
352
|
-
|
363
|
+
it "should return same enum without block" do
|
364
|
+
enum.in_threads.cycle(3).to_a.should == enum.cycle(3).to_a
|
365
|
+
end
|
353
366
|
end
|
354
367
|
|
355
|
-
|
356
|
-
|
357
|
-
end
|
358
|
-
end
|
368
|
+
describe "grep" do
|
369
|
+
let(:matcher){ Item::MiddleMatcher.new }
|
359
370
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
def each
|
364
|
-
10.times{ yield 1 }
|
365
|
-
10.times{ yield 2, 3 }
|
366
|
-
10.times{ yield }
|
371
|
+
it "should fire same objects" do
|
372
|
+
enum.each{ |o| o.should_receive(:touch).exactly(matcher === o ? 1 : 0).times }
|
373
|
+
enum.in_threads.grep(matcher, &:touch_n_value)
|
367
374
|
end
|
368
|
-
end
|
369
|
-
|
370
|
-
let(:enum){ EachEntryYielder.new }
|
371
|
-
let(:runner){ proc{ |o| ValueItem.new(0, o).work } }
|
372
375
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
+
it "should return same with block" do
|
377
|
+
enum.in_threads.grep(matcher, &:value).should == enum.grep(matcher, &:value)
|
378
|
+
end
|
376
379
|
|
377
|
-
|
378
|
-
|
379
|
-
@order.should_receive(:notify).with(1).exactly(10).times.ordered
|
380
|
-
@order.should_receive(:notify).with([2, 3]).exactly(10).times.ordered
|
381
|
-
@order.should_receive(:notify).with(nil).exactly(10).times.ordered
|
382
|
-
@mutex = Mutex.new
|
383
|
-
enum.in_threads.each_entry do |o|
|
384
|
-
@mutex.synchronize{ @order.notify(o) }
|
385
|
-
runner[]
|
380
|
+
it "should run faster with threads" do
|
381
|
+
measure{ enum.in_threads.grep(matcher, &:value) }.should < measure{ enum.grep(matcher, &:value) } * speed_coef
|
386
382
|
end
|
387
|
-
end
|
388
383
|
|
389
|
-
|
390
|
-
|
384
|
+
it "should return same without block" do
|
385
|
+
enum.in_threads.grep(matcher).should == enum.grep(matcher)
|
386
|
+
end
|
391
387
|
end
|
392
388
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
389
|
+
describe_enum_method "each_entry" do
|
390
|
+
class EachEntryYielder
|
391
|
+
include Enumerable
|
392
|
+
def each
|
393
|
+
10.times{ yield 1 }
|
394
|
+
10.times{ yield 2, 3 }
|
395
|
+
10.times{ yield }
|
396
|
+
end
|
397
|
+
end
|
397
398
|
|
398
|
-
|
399
|
-
|
400
|
-
let(:enum){ 20.times.map{ |i| Item.new(i) }.each_slice(3) }
|
401
|
-
let(:runner){ proc{ |a| a.map(&:value) } }
|
399
|
+
let(:enum){ EachEntryYielder.new }
|
400
|
+
let(:runner){ proc{ |o| ValueItem.new(0, o).value } }
|
402
401
|
|
403
402
|
it "should return same result with threads" do
|
404
|
-
enum.in_threads.
|
403
|
+
enum.in_threads.each_entry(&runner).should == enum.each_entry(&runner)
|
405
404
|
end
|
406
405
|
|
407
|
-
it "should
|
408
|
-
|
409
|
-
|
406
|
+
it "should execute block for each element" do
|
407
|
+
@order = mock('order')
|
408
|
+
@order.should_receive(:notify).with(1).exactly(10).times.ordered
|
409
|
+
@order.should_receive(:notify).with([2, 3]).exactly(10).times.ordered
|
410
|
+
@order.should_receive(:notify).with(nil).exactly(10).times.ordered
|
411
|
+
@mutex = Mutex.new
|
412
|
+
enum.in_threads.each_entry do |o|
|
413
|
+
@mutex.synchronize{ @order.notify(o) }
|
414
|
+
runner[]
|
415
|
+
end
|
410
416
|
end
|
411
417
|
|
412
418
|
it "should run faster with threads" do
|
413
|
-
measure{ enum.in_threads.
|
419
|
+
measure{ enum.in_threads.each_entry(&runner) }.should < measure{ enum.each_entry(&runner) } * speed_coef
|
414
420
|
end
|
415
421
|
|
416
422
|
it "should return same enum without block" do
|
417
|
-
enum.in_threads.
|
423
|
+
enum.in_threads.each_entry.to_a.should == enum.each_entry.to_a
|
418
424
|
end
|
419
425
|
end
|
420
|
-
end
|
421
426
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
427
|
+
%w[flat_map collect_concat].each do |method|
|
428
|
+
describe_enum_method method do
|
429
|
+
let(:enum){ 20.times.map{ |i| Item.new(i) }.each_slice(3) }
|
430
|
+
let(:runner){ proc{ |a| a.map(&:value) } }
|
431
|
+
|
432
|
+
it "should return same result with threads" do
|
433
|
+
enum.in_threads.send(method, &runner).should == enum.send(method, &runner)
|
428
434
|
end
|
429
|
-
end
|
430
|
-
end
|
431
435
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
436
|
+
it "should fire same objects" do
|
437
|
+
enum.send(method){ |a| a.each{ |o| o.should_receive(:touch).with(a).once } }
|
438
|
+
enum.in_threads.send(method){ |a| a.each{ |o| o.touch_n_value(a) } }
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should run faster with threads" do
|
442
|
+
measure{ enum.in_threads.send(method, &runner) }.should < measure{ enum.send(method, &runner) } * speed_coef
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should return same enum without block" do
|
446
|
+
enum.in_threads.send(method).to_a.should == enum.send(method).to_a
|
437
447
|
end
|
438
448
|
end
|
439
449
|
end
|
440
450
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
451
|
+
context "unthreaded" do
|
452
|
+
%w[inject reduce].each do |method|
|
453
|
+
describe method do
|
454
|
+
it "should return same result" do
|
455
|
+
combiner = proc{ |memo, o| memo + o.value }
|
456
|
+
enum.in_threads.send(method, 0, &combiner).should == enum.send(method, 0, &combiner)
|
457
|
+
end
|
445
458
|
end
|
446
459
|
end
|
447
|
-
end
|
448
460
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
461
|
+
%w[max min minmax sort].each do |method|
|
462
|
+
describe method do
|
463
|
+
it "should return same result" do
|
464
|
+
comparer = proc{ |a, b| a.value <=> b.value }
|
465
|
+
enum.in_threads.send(method, &comparer).should == enum.send(method, &comparer)
|
466
|
+
end
|
453
467
|
end
|
454
468
|
end
|
455
|
-
end
|
456
469
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
470
|
+
%w[to_a entries].each do |method|
|
471
|
+
describe method do
|
472
|
+
it "should return same result" do
|
473
|
+
enum.in_threads.send(method).should == enum.send(method)
|
474
|
+
end
|
462
475
|
end
|
463
476
|
end
|
464
|
-
end
|
465
477
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
478
|
+
%w[drop take].each do |method|
|
479
|
+
describe method do
|
480
|
+
it "should return same result" do
|
481
|
+
enum.in_threads.send(method, 2).should == enum.send(method, 2)
|
482
|
+
end
|
470
483
|
end
|
471
484
|
end
|
472
|
-
end
|
473
485
|
|
474
|
-
|
475
|
-
|
486
|
+
%w[first].each do |method|
|
487
|
+
describe method do
|
488
|
+
it "should return same result" do
|
489
|
+
enum.in_threads.send(method).should == enum.send(method)
|
490
|
+
enum.in_threads.send(method, 3).should == enum.send(method, 3)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
476
494
|
|
477
|
-
|
478
|
-
|
495
|
+
%w[include? member?].each do |method|
|
496
|
+
describe method do
|
497
|
+
it "should return same result" do
|
498
|
+
enum.in_threads.send(method, enum[10]).should == enum.send(method, enum[10])
|
499
|
+
end
|
500
|
+
end
|
479
501
|
end
|
480
|
-
end
|
481
502
|
|
482
|
-
|
483
|
-
|
503
|
+
describe_enum_method "each_with_object" do
|
504
|
+
let(:runner){ proc{ |o, h| h[o.value] = true } }
|
505
|
+
|
484
506
|
it "should return same result" do
|
485
|
-
enum.in_threads.
|
507
|
+
enum.in_threads.each_with_object({}, &runner).should == enum.each_with_object({}, &runner)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
%w[chunk slice_before].each do |method|
|
512
|
+
describe_enum_method method do
|
513
|
+
it "should return same result" do
|
514
|
+
enum.in_threads.send(method, &:check?).to_a.should == enum.send(method, &:check?).to_a
|
515
|
+
end
|
486
516
|
end
|
487
517
|
end
|
488
518
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: in_threads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 1.1.
|
9
|
+
- 1
|
10
|
+
version: 1.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ivan Kuchin
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-12-
|
18
|
+
date: 2011-12-13 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rspec
|
@@ -45,6 +45,8 @@ files:
|
|
45
45
|
- README.markdown
|
46
46
|
- in_threads.gemspec
|
47
47
|
- lib/in_threads.rb
|
48
|
+
- lib/in_threads/filler.rb
|
49
|
+
- lib/in_threads/thread_limiter.rb
|
48
50
|
- spec/in_threads_spec.rb
|
49
51
|
- spec/spec_helper.rb
|
50
52
|
homepage: http://github.com/toy/in_threads
|
@@ -76,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
78
|
requirements: []
|
77
79
|
|
78
80
|
rubyforge_project: in_threads
|
79
|
-
rubygems_version: 1.8.
|
81
|
+
rubygems_version: 1.8.12
|
80
82
|
signing_key:
|
81
83
|
specification_version: 3
|
82
84
|
summary: Execute ruby code in parallel
|