concur 0.0.4 → 0.0.6
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/README.markdown +29 -1
- data/lib/concur.rb +14 -14
- data/lib/executor.rb +86 -73
- data/lib/executors/event_machine_executor.rb +34 -0
- data/lib/executors/never_block_executor.rb +27 -0
- data/lib/future.rb +64 -53
- data/lib/futures/event_machine_future.rb +164 -0
- data/lib/runnable.rb +13 -13
- data/lib/thread_pool.rb +62 -62
- data/test/executor_spec.rb +87 -82
- data/test/job.rb +50 -15
- metadata +32 -36
data/README.markdown
CHANGED
@@ -1 +1,29 @@
|
|
1
|
-
# Concur - A concurrency library for Ruby inspired by java.util.concurrency
|
1
|
+
# Concur - A concurrency library for Ruby inspired by java.util.concurrency
|
2
|
+
|
3
|
+
## General Usage
|
4
|
+
|
5
|
+
# Choose which executor you want, there are several to choose from
|
6
|
+
executor = Concur::Executor.new_thread_pool_executor(10)
|
7
|
+
start_time = Time.now
|
8
|
+
|
9
|
+
jobs = []
|
10
|
+
times.times do |i|
|
11
|
+
future = executor.execute do
|
12
|
+
puts "hello #{i}"
|
13
|
+
"result #{i}"
|
14
|
+
end
|
15
|
+
jobs << future
|
16
|
+
end
|
17
|
+
jobs.each do |j|
|
18
|
+
puts "uber fast result=#{j.get}"
|
19
|
+
end
|
20
|
+
pooled_duration = Time.now - start_time
|
21
|
+
puts "pooled_duration=" + pooled_duration.to_s
|
22
|
+
executor.shutdown
|
23
|
+
|
24
|
+
## Futures
|
25
|
+
|
26
|
+
A Future is what is returned from the execute method. Call `future.get` to get the results of the block
|
27
|
+
or the Callable object. If it's not finished, `get` will block until it is. `get` will also raise an Exception
|
28
|
+
if an Exception occurred during running.
|
29
|
+
|
data/lib/concur.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
|
2
|
-
require_relative 'executor'
|
3
|
-
|
4
|
-
require 'logger'
|
5
|
-
module Concur
|
6
|
-
@@logger = Logger.new(STDOUT)
|
7
|
-
|
8
|
-
def self.logger
|
9
|
-
@@logger
|
10
|
-
end
|
11
|
-
def self.logger=(logger)
|
12
|
-
@@logger = logger
|
13
|
-
end
|
14
|
-
|
1
|
+
|
2
|
+
require_relative 'executor'
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
module Concur
|
6
|
+
@@logger = Logger.new(STDOUT)
|
7
|
+
|
8
|
+
def self.logger
|
9
|
+
@@logger
|
10
|
+
end
|
11
|
+
def self.logger=(logger)
|
12
|
+
@@logger = logger
|
13
|
+
end
|
14
|
+
|
15
15
|
end
|
data/lib/executor.rb
CHANGED
@@ -1,73 +1,86 @@
|
|
1
|
-
require_relative 'runnable'
|
2
|
-
require_relative 'future'
|
3
|
-
require_relative 'thread_pool'
|
4
|
-
|
5
|
-
|
6
|
-
module Concur
|
7
|
-
|
8
|
-
|
9
|
-
# Decouples task submission from how each task is run. An Executor can be backed by a thread pool or some
|
10
|
-
# other mechanism, but how you use the Executor won't change. This allows you to change the backend implementation
|
11
|
-
# with minor code changes.
|
12
|
-
#
|
13
|
-
# Inspired by java.util.concurrent.Executor
|
14
|
-
class Executor
|
15
|
-
|
16
|
-
attr_accessor :thread_pool
|
17
|
-
|
18
|
-
def initialize(options={})
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.new_single_threaded_executor(options={})
|
23
|
-
executor = Executor.new
|
24
|
-
executor.thread_pool = SingleThreaded.new
|
25
|
-
executor
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.new_multi_threaded_executor(options={})
|
29
|
-
executor = Executor.new
|
30
|
-
executor.thread_pool = MultiThreaded.new
|
31
|
-
executor
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.new_thread_pool_executor(max_size, options={})
|
35
|
-
executor = Executor.new
|
36
|
-
executor.thread_pool = ThreadPool.new(max_size)
|
37
|
-
executor
|
38
|
-
end
|
39
|
-
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def
|
54
|
-
f.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
1
|
+
require_relative 'runnable'
|
2
|
+
require_relative 'future'
|
3
|
+
require_relative 'thread_pool'
|
4
|
+
|
5
|
+
|
6
|
+
module Concur
|
7
|
+
|
8
|
+
|
9
|
+
# Decouples task submission from how each task is run. An Executor can be backed by a thread pool or some
|
10
|
+
# other mechanism, but how you use the Executor won't change. This allows you to change the backend implementation
|
11
|
+
# with minor code changes.
|
12
|
+
#
|
13
|
+
# Inspired by java.util.concurrent.Executor
|
14
|
+
class Executor
|
15
|
+
|
16
|
+
attr_accessor :thread_pool
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.new_single_threaded_executor(options={})
|
23
|
+
executor = Executor.new
|
24
|
+
executor.thread_pool = SingleThreaded.new
|
25
|
+
executor
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new_multi_threaded_executor(options={})
|
29
|
+
executor = Executor.new
|
30
|
+
executor.thread_pool = MultiThreaded.new
|
31
|
+
executor
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.new_thread_pool_executor(max_size, options={})
|
35
|
+
executor = Executor.new
|
36
|
+
executor.thread_pool = ThreadPool.new(max_size)
|
37
|
+
executor
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.new_eventmachine_executor()
|
41
|
+
require_relative 'executors/event_machine_executor'
|
42
|
+
executor = EventMachineExecutor.new()
|
43
|
+
executor
|
44
|
+
end
|
45
|
+
|
46
|
+
# NOT WORKING
|
47
|
+
def self.new_neverblock_executor(max_size)
|
48
|
+
require_relative 'executors/never_block_executor'
|
49
|
+
executor = NeverBlockExecutor.new(max_size)
|
50
|
+
executor
|
51
|
+
end
|
52
|
+
|
53
|
+
def execute(runnable=nil, &blk)
|
54
|
+
f = StandardFuture.new(runnable, &blk)
|
55
|
+
@thread_pool.process(f)
|
56
|
+
f
|
57
|
+
end
|
58
|
+
|
59
|
+
def shutdown
|
60
|
+
@thread_pool.shutdown
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# todo: should maybe have these backends extend Executor and just override what's necessary
|
65
|
+
class SingleThreaded
|
66
|
+
def process(f)
|
67
|
+
f.call
|
68
|
+
end
|
69
|
+
|
70
|
+
def shutdown
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MultiThreaded
|
75
|
+
def process(f)
|
76
|
+
@thread = Thread.new do
|
77
|
+
f.thread = @thread
|
78
|
+
f.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def shutdown
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require_relative '../futures/event_machine_future'
|
3
|
+
|
4
|
+
module Concur
|
5
|
+
class EventMachineExecutor
|
6
|
+
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@futures = []
|
10
|
+
@em_thread = Thread.new do
|
11
|
+
EventMachine.run do
|
12
|
+
puts 'Starting EventMachineExecutor...'
|
13
|
+
# @futures.each do |f|
|
14
|
+
#
|
15
|
+
# end
|
16
|
+
end
|
17
|
+
puts 'EventMachine loop done'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(runnable=nil, &blk)
|
22
|
+
f = EventMachineFuture.new(runnable, &blk)
|
23
|
+
# @futures = f
|
24
|
+
EventMachine.schedule(f)
|
25
|
+
f
|
26
|
+
end
|
27
|
+
|
28
|
+
def shutdown
|
29
|
+
@em_thread.kill
|
30
|
+
puts 'shutdown'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'neverblock'
|
3
|
+
|
4
|
+
# This doesn't work as expected. Not sure how to get it to non block on the IO calls
|
5
|
+
|
6
|
+
module Concur
|
7
|
+
class NeverBlockExecutor
|
8
|
+
|
9
|
+
def initialize(max_size)
|
10
|
+
@fiber_pool = NeverBlock::Pool::FiberPool.new(max_size)
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(runnable=nil, &blk)
|
14
|
+
f = Future.new(runnable, &blk)
|
15
|
+
@fiber_pool.spawn do
|
16
|
+
puts 'running in spawn'
|
17
|
+
f.call
|
18
|
+
end
|
19
|
+
f
|
20
|
+
end
|
21
|
+
|
22
|
+
def shutdown
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/future.rb
CHANGED
@@ -1,53 +1,64 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
1
|
+
#An Future is a class that captures the results of a threaded object so you can retrieve the results later.
|
2
|
+
#This is what is returned from Executors.
|
3
|
+
#
|
4
|
+
# This particular Future can be used for synchronous blocks / runnables / callables that are run in a separate thread.
|
5
|
+
|
6
|
+
module Concur
|
7
|
+
|
8
|
+
module Future
|
9
|
+
def future?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class StandardFuture
|
15
|
+
include Future
|
16
|
+
|
17
|
+
attr_accessor :thread, :ex
|
18
|
+
|
19
|
+
def initialize(runnable=nil, &block)
|
20
|
+
|
21
|
+
@mutex = Mutex.new
|
22
|
+
@cv = ConditionVariable.new
|
23
|
+
@callable = runnable
|
24
|
+
if block_given?
|
25
|
+
@callable = block
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
begin
|
32
|
+
@result = @callable.call
|
33
|
+
rescue Exception => ex
|
34
|
+
@ex = ex
|
35
|
+
end
|
36
|
+
@mutex.synchronize do # do we even need to synchronize? run should only ever be called once
|
37
|
+
@complete = true
|
38
|
+
@cv.broadcast
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def call
|
43
|
+
run
|
44
|
+
end
|
45
|
+
|
46
|
+
def complete?
|
47
|
+
@complete
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns results. Will wait for thread to complete execution if not already complete.
|
51
|
+
def get
|
52
|
+
# @thread.value
|
53
|
+
@mutex.synchronize do
|
54
|
+
unless @complete
|
55
|
+
@cv.wait(@mutex)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if @ex
|
59
|
+
raise @ex
|
60
|
+
end
|
61
|
+
@result
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# This future enables a blocking get for a result from EventMachine HTTP request.
|
2
|
+
|
3
|
+
module Concur
|
4
|
+
|
5
|
+
class EventMachineError < StandardError
|
6
|
+
|
7
|
+
def initialize(callbackable)
|
8
|
+
super("Error in #{callbackable.class.name}")
|
9
|
+
@callbackable = callbackable
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class EventMachineFutureCallback
|
15
|
+
|
16
|
+
def future?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :errblk, :callblk
|
21
|
+
|
22
|
+
def initialize(callbackable, &block)
|
23
|
+
puts 'EventMachineFutureCallback.initialize: ' + callbackable.inspect
|
24
|
+
@callbackable = callbackable
|
25
|
+
if block_given?
|
26
|
+
@callblk = block
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
|
32
|
+
# http = @callbackable
|
33
|
+
# http.errback {
|
34
|
+
# @ex = EventMachineError.new(http)
|
35
|
+
# complete
|
36
|
+
# }
|
37
|
+
# http.callback {
|
38
|
+
# if @callback
|
39
|
+
# @callback.call(@callbackable)
|
40
|
+
# end
|
41
|
+
# complete
|
42
|
+
# }
|
43
|
+
|
44
|
+
|
45
|
+
end
|
46
|
+
def errback &blk
|
47
|
+
@errblk = blk
|
48
|
+
end
|
49
|
+
|
50
|
+
def callback &blk
|
51
|
+
@callblk = blk
|
52
|
+
end
|
53
|
+
|
54
|
+
def errback2 &blk
|
55
|
+
puts 'EventMachineFutureCallback.errback'
|
56
|
+
proc = Proc.new do
|
57
|
+
puts 'errback proc'
|
58
|
+
blk.call(@callbackable)
|
59
|
+
end
|
60
|
+
puts 'setting errback on ' + @callbackable.inspect
|
61
|
+
@callbackable.errback &proc
|
62
|
+
puts 'errback set'
|
63
|
+
end
|
64
|
+
|
65
|
+
def callback2 &blk
|
66
|
+
puts 'EventMachineFutureCallback.callback'
|
67
|
+
proc = Proc.new do
|
68
|
+
@result = []
|
69
|
+
if @callblk
|
70
|
+
@result = @callblk.call(@callbackable)
|
71
|
+
end
|
72
|
+
blk.call(@result)
|
73
|
+
end
|
74
|
+
@callbackable.callback &proc
|
75
|
+
end
|
76
|
+
#
|
77
|
+
# def call_callback(response)
|
78
|
+
# puts 'call_callback=' + response.inspect
|
79
|
+
# return unless callblk
|
80
|
+
# callblk.call(response)
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# def call_errback(response)
|
84
|
+
# return unless errblk
|
85
|
+
# errblk.call(response)
|
86
|
+
# end
|
87
|
+
end
|
88
|
+
|
89
|
+
class EventMachineFuture
|
90
|
+
include Concur::Future
|
91
|
+
|
92
|
+
attr_accessor :ex,:result
|
93
|
+
|
94
|
+
def initialize(callable, &block)
|
95
|
+
|
96
|
+
@mutex = Mutex.new
|
97
|
+
@cv = ConditionVariable.new
|
98
|
+
@callable = callable
|
99
|
+
if block_given?
|
100
|
+
@callable = block
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def run
|
106
|
+
puts 'EMFuture.run'
|
107
|
+
p @callable
|
108
|
+
begin
|
109
|
+
@callbackable = @callable.call
|
110
|
+
puts 'done @callable.call ' + @callbackable.inspect
|
111
|
+
rescue Exception => ex
|
112
|
+
@ex = ex
|
113
|
+
end
|
114
|
+
if @ex
|
115
|
+
complete
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
http = @callbackable
|
120
|
+
http.errback2 {
|
121
|
+
puts 'completion errback'
|
122
|
+
@ex = EventMachineError.new(http)
|
123
|
+
complete
|
124
|
+
}
|
125
|
+
@result = (http.callback2 {|result|
|
126
|
+
@result = result
|
127
|
+
complete
|
128
|
+
})
|
129
|
+
puts '@result=' + @result.inspect
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
def call
|
134
|
+
run
|
135
|
+
end
|
136
|
+
|
137
|
+
def complete
|
138
|
+
@complete = true
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def complete?
|
143
|
+
@complete
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns results. Will wait for thread to complete execution if not already complete.
|
147
|
+
def get
|
148
|
+
# @thread.value
|
149
|
+
while not @complete
|
150
|
+
# todo: gotta be a better way
|
151
|
+
puts 'sleeping'
|
152
|
+
sleep 0.5
|
153
|
+
end
|
154
|
+
return get_response
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_response
|
158
|
+
if @ex
|
159
|
+
raise @ex
|
160
|
+
end
|
161
|
+
@result
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/lib/runnable.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
|
2
|
-
# A mixin for runnable classes.
|
3
|
-
module Concur
|
4
|
-
module Runnable
|
5
|
-
def run
|
6
|
-
raise "No run method defined in your runable!"
|
7
|
-
end
|
8
|
-
|
9
|
-
def call
|
10
|
-
run
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
1
|
+
|
2
|
+
# A mixin for runnable classes.
|
3
|
+
module Concur
|
4
|
+
module Runnable
|
5
|
+
def run
|
6
|
+
raise "No run method defined in your runable!"
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
run
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/thread_pool.rb
CHANGED
@@ -1,62 +1,62 @@
|
|
1
|
-
require 'thread'
|
2
|
-
begin
|
3
|
-
require 'fastthread'
|
4
|
-
rescue LoadError
|
5
|
-
$stderr.puts "Using the ruby-core thread implementation"
|
6
|
-
end
|
7
|
-
|
8
|
-
module Concur
|
9
|
-
|
10
|
-
# Another example is here: # from: http://stackoverflow.com/questions/81788/deadlock-in-threadpool
|
11
|
-
class ThreadPool
|
12
|
-
def initialize(max_size)
|
13
|
-
@max_size = max_size
|
14
|
-
# @thread_queue = SizedQueue.new(max_size)
|
15
|
-
@running = true
|
16
|
-
@mutex = Mutex.new
|
17
|
-
@cv = ConditionVariable.new
|
18
|
-
@queue = Queue.new
|
19
|
-
@threads = []
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def shutdown
|
24
|
-
@running = false
|
25
|
-
end
|
26
|
-
|
27
|
-
def process(callable, &blk)
|
28
|
-
callable = blk if block_given?
|
29
|
-
@queue.push(callable)
|
30
|
-
start_thread
|
31
|
-
end
|
32
|
-
|
33
|
-
def start_thread
|
34
|
-
@mutex.synchronize do
|
35
|
-
if !@queue.empty? && @threads.size <= @max_size
|
36
|
-
t = UberThread.new do
|
37
|
-
while @running
|
38
|
-
f = @queue.pop
|
39
|
-
f.thread = t
|
40
|
-
f.call
|
41
|
-
end
|
42
|
-
# Concur.logger.info "Thread dying " + t.inspect
|
43
|
-
end
|
44
|
-
Concur.logger.debug "Created new thread " + t.inspect
|
45
|
-
@threads << t
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
class UberThread < Thread
|
51
|
-
|
52
|
-
def initialize
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
begin
|
3
|
+
require 'fastthread'
|
4
|
+
rescue LoadError
|
5
|
+
$stderr.puts "Using the ruby-core thread implementation"
|
6
|
+
end
|
7
|
+
|
8
|
+
module Concur
|
9
|
+
|
10
|
+
# Another example is here: # from: http://stackoverflow.com/questions/81788/deadlock-in-threadpool
|
11
|
+
class ThreadPool
|
12
|
+
def initialize(max_size)
|
13
|
+
@max_size = max_size
|
14
|
+
# @thread_queue = SizedQueue.new(max_size)
|
15
|
+
@running = true
|
16
|
+
@mutex = Mutex.new
|
17
|
+
@cv = ConditionVariable.new
|
18
|
+
@queue = Queue.new
|
19
|
+
@threads = []
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def shutdown
|
24
|
+
@running = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def process(callable, &blk)
|
28
|
+
callable = blk if block_given?
|
29
|
+
@queue.push(callable)
|
30
|
+
start_thread
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_thread
|
34
|
+
@mutex.synchronize do
|
35
|
+
if !@queue.empty? && @threads.size <= @max_size
|
36
|
+
t = UberThread.new do
|
37
|
+
while @running
|
38
|
+
f = @queue.pop
|
39
|
+
f.thread = t
|
40
|
+
f.call
|
41
|
+
end
|
42
|
+
# Concur.logger.info "Thread dying " + t.inspect
|
43
|
+
end
|
44
|
+
Concur.logger.debug "Created new thread " + t.inspect
|
45
|
+
@threads << t
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class UberThread < Thread
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
end
|
data/test/executor_spec.rb
CHANGED
@@ -1,82 +1,87 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
|
3
|
-
|
4
|
-
require_relative '
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
executor = Concur::Executor.
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
future
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
1
|
+
require 'rspec'
|
2
|
+
require 'neverblock'
|
3
|
+
|
4
|
+
require_relative '../lib/concur'
|
5
|
+
require_relative 'job'
|
6
|
+
|
7
|
+
@@durations = []
|
8
|
+
|
9
|
+
def run_jobs(name, executor, times, options={})
|
10
|
+
puts "Running #{name}..."
|
11
|
+
start_time = Time.now
|
12
|
+
|
13
|
+
jobs = []
|
14
|
+
times.times do |i|
|
15
|
+
job = Job.new(i, options)
|
16
|
+
jobs << executor.execute(job)
|
17
|
+
end
|
18
|
+
jobs.each do |j|
|
19
|
+
puts "result=#{j.get}"
|
20
|
+
end
|
21
|
+
concurrent_duration = Time.now - start_time
|
22
|
+
o = "#{name} duration=" + concurrent_duration.to_s
|
23
|
+
puts o
|
24
|
+
@@durations << o
|
25
|
+
concurrent_duration
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Concur::Executor do
|
29
|
+
describe "#score" do
|
30
|
+
it "runs faster in parallel" do
|
31
|
+
times = 10
|
32
|
+
|
33
|
+
job = Job.new(1)
|
34
|
+
puts 'runnable? ' + job.is_a?(Concur::Runnable).to_s
|
35
|
+
non_concurrent_duration = 0
|
36
|
+
#
|
37
|
+
executor = Concur::Executor.new_single_threaded_executor
|
38
|
+
non_concurrent_duration =run_jobs("non concurrent", executor, times)
|
39
|
+
executor.shutdown
|
40
|
+
|
41
|
+
executor = Concur::Executor.new_multi_threaded_executor
|
42
|
+
concurrent_duration = run_jobs("multi thread", executor, times)
|
43
|
+
concurrent_duration.should be < (non_concurrent_duration/2)
|
44
|
+
executor.shutdown
|
45
|
+
|
46
|
+
executor = Concur::Executor.new_thread_pool_executor(10)
|
47
|
+
pooled_duration = run_jobs("thread pool", executor, times)
|
48
|
+
pooled_duration.should be < (non_concurrent_duration/2)
|
49
|
+
executor.shutdown
|
50
|
+
|
51
|
+
# Don't think I know how to use NeverBlock properly
|
52
|
+
# executor = Concur::Executor.new_neverblock_executor(10)
|
53
|
+
# neverblock_duration = run_jobs("never blocked", executor, times)
|
54
|
+
# neverblock_duration.should be < (non_concurrent_duration/2)
|
55
|
+
# executor.shutdown
|
56
|
+
|
57
|
+
executor = Concur::Executor.new_eventmachine_executor()
|
58
|
+
em_duration = run_jobs("eventmachine", executor, times, :em=>true)
|
59
|
+
em_duration.should be < (non_concurrent_duration/2)
|
60
|
+
executor.shutdown
|
61
|
+
|
62
|
+
@@durations.each do |s|
|
63
|
+
puts s
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe Concur::Future do
|
71
|
+
|
72
|
+
describe "#new" do
|
73
|
+
|
74
|
+
it "can accept blocks" do
|
75
|
+
future = Concur::Future.new do
|
76
|
+
puts "i'm in the block"
|
77
|
+
"result of block"
|
78
|
+
end
|
79
|
+
puts 'get=' + future.get
|
80
|
+
future.get.should == "result of block"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
data/test/job.rb
CHANGED
@@ -1,16 +1,51 @@
|
|
1
|
-
require_relative '../lib/runnable'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
1
|
+
require_relative '../lib/runnable'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
class Job
|
6
|
+
include Concur::Runnable
|
7
|
+
|
8
|
+
def initialize(i, options={})
|
9
|
+
@i = i
|
10
|
+
if options[:em]
|
11
|
+
@em = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def em_request(url)
|
16
|
+
puts 'emrequest for ' + url
|
17
|
+
http = EventMachine::Protocols::HttpClient.request(
|
18
|
+
:host => url,
|
19
|
+
:port => 80,
|
20
|
+
:request => "/"
|
21
|
+
)
|
22
|
+
http.callback { |response|
|
23
|
+
puts response[:status]
|
24
|
+
puts response[:headers]
|
25
|
+
puts response[:content]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
puts "Starting #{@i}... em? #{@em}"
|
31
|
+
# sleep 1
|
32
|
+
urls = ["www.yahoo.com", "www.microsoft.com"] # , "www.github.com"
|
33
|
+
if @em
|
34
|
+
urls.each do |u|
|
35
|
+
em_request(u)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
urls.each do |u|
|
39
|
+
get(u)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
puts "Finished #{@i}"
|
43
|
+
"response #{@i}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(url)
|
47
|
+
puts 'getting ' + url
|
48
|
+
RestClient.get "http://#{url}"
|
49
|
+
end
|
50
|
+
|
16
51
|
end
|
metadata
CHANGED
@@ -1,69 +1,65 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: concur
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.4
|
3
|
+
version: !ruby/object:Gem::Version
|
5
4
|
prerelease:
|
5
|
+
version: 0.0.6
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Travis Reeder
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
17
|
-
requirement: &25999668 !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
|
-
requirements:
|
20
|
-
- - ! '>='
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: *25999668
|
26
|
-
description: A concurrency library for Ruby inspired by java.util.concurrency. By
|
27
|
-
http://www.appoxy.com
|
12
|
+
|
13
|
+
date: 2011-05-02 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A concurrency library for Ruby inspired by java.util.concurrency. By http://www.appoxy.com
|
28
17
|
email: travis@appoxy.com
|
29
18
|
executables: []
|
19
|
+
|
30
20
|
extensions: []
|
31
|
-
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
32
23
|
- README.markdown
|
33
|
-
files:
|
24
|
+
files:
|
34
25
|
- lib/concur.rb
|
35
26
|
- lib/executor.rb
|
27
|
+
- lib/executors/event_machine_executor.rb
|
28
|
+
- lib/executors/never_block_executor.rb
|
36
29
|
- lib/future.rb
|
30
|
+
- lib/futures/event_machine_future.rb
|
37
31
|
- lib/runnable.rb
|
38
32
|
- lib/thread_pool.rb
|
39
33
|
- README.markdown
|
40
34
|
- test/executor_spec.rb
|
41
35
|
- test/job.rb
|
42
|
-
has_rdoc: true
|
43
36
|
homepage: http://github.com/appoxy/concur/
|
44
37
|
licenses: []
|
38
|
+
|
45
39
|
post_install_message:
|
46
40
|
rdoc_options: []
|
47
|
-
|
41
|
+
|
42
|
+
require_paths:
|
48
43
|
- lib
|
49
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
45
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
51
|
none: false
|
57
|
-
requirements:
|
58
|
-
- -
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version:
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
61
56
|
requirements: []
|
57
|
+
|
62
58
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.
|
59
|
+
rubygems_version: 1.7.2
|
64
60
|
signing_key:
|
65
61
|
specification_version: 3
|
66
62
|
summary: A concurrency library for Ruby inspired by java.util.concurrency. By http://www.appoxy.com
|
67
|
-
test_files:
|
63
|
+
test_files:
|
68
64
|
- test/executor_spec.rb
|
69
65
|
- test/job.rb
|