concur 0.0.1 → 0.0.2
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/lib/concur.rb +13 -1
- data/lib/executor.rb +41 -2
- data/lib/future.rb +38 -16
- data/lib/runnable.rb +4 -1
- data/lib/thread_pool.rb +63 -0
- data/test/executor_spec.rb +52 -7
- data/test/job.rb +1 -1
- metadata +16 -4
data/lib/concur.rb
CHANGED
data/lib/executor.rb
CHANGED
@@ -1,10 +1,49 @@
|
|
1
1
|
require_relative 'runnable'
|
2
2
|
require_relative 'future'
|
3
|
+
require_relative 'thread_pool'
|
4
|
+
|
3
5
|
|
4
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
|
5
14
|
class Executor
|
6
|
-
|
7
|
-
|
15
|
+
|
16
|
+
attr_accessor :thread_pool
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.new_thread_pool_executor(options={})
|
23
|
+
executor = Executor.new
|
24
|
+
executor.thread_pool = ThreadPool.new(options[:max_size])
|
25
|
+
p executor.thread_pool
|
26
|
+
executor
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute(runnable, &blk)
|
30
|
+
puts 'executing ' + runnable.inspect
|
31
|
+
f = Future.new(runnable, &blk)
|
32
|
+
if @thread_pool
|
33
|
+
@thread_pool.process(f)
|
34
|
+
else
|
35
|
+
@thread = Thread.new do
|
36
|
+
f.thread = @thread
|
37
|
+
f.call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
f
|
41
|
+
end
|
42
|
+
|
43
|
+
def shutdown
|
44
|
+
if @thread_pool
|
45
|
+
@thread_pool.shutdown
|
46
|
+
end
|
8
47
|
end
|
9
48
|
end
|
10
49
|
end
|
data/lib/future.rb
CHANGED
@@ -1,27 +1,49 @@
|
|
1
|
-
|
2
|
-
An Future is a class that captures the results of a threaded object so you can retreive these results later.
|
1
|
+
module Concur
|
3
2
|
|
4
|
-
|
3
|
+
#An Future is a class that captures the results of a threaded object so you can retreive the results later.
|
4
|
+
#This is what is returned from Executors.
|
5
|
+
class Future
|
6
|
+
attr_accessor :thread, :pool
|
5
7
|
|
6
|
-
|
7
|
-
# todo: not sure if this will be usage in the end.... but
|
8
|
-
future = Future.new(searcher)
|
9
|
-
do_some_other_stuff_in_the_meantime()
|
10
|
-
search_results = future.get
|
8
|
+
def initialize(runnable=nil, &block)
|
11
9
|
|
12
|
-
|
13
|
-
=
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
# if block
|
11
|
+
# @thread = Thread.new do
|
12
|
+
# @result = yield
|
13
|
+
# end
|
14
|
+
# else
|
15
|
+
# @thread = Thread.new do
|
16
|
+
# @result = runnable.run
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@cv = ConditionVariable.new
|
22
|
+
@callable = runnable
|
23
|
+
if block_given?
|
24
|
+
@callable = block
|
19
25
|
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
@result = @callable.call
|
31
|
+
@complete = true
|
32
|
+
@cv.signal
|
33
|
+
end
|
34
|
+
|
35
|
+
def call
|
36
|
+
run
|
20
37
|
end
|
21
38
|
|
22
39
|
# Returns results. Will wait for thread to complete execution if not already complete.
|
23
40
|
def get
|
24
|
-
@thread.
|
41
|
+
# @thread.value
|
42
|
+
@mutex.synchronize do
|
43
|
+
unless @complete
|
44
|
+
@cv.wait(@mutex)
|
45
|
+
end
|
46
|
+
end
|
25
47
|
@result
|
26
48
|
end
|
27
49
|
end
|
data/lib/runnable.rb
CHANGED
data/lib/thread_pool.rb
ADDED
@@ -0,0 +1,63 @@
|
|
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.pool = self
|
41
|
+
f.call
|
42
|
+
end
|
43
|
+
Concur.logger.info "Thread dying " + t.inspect
|
44
|
+
end
|
45
|
+
Concur.logger.info "Created new thread " + t.inspect
|
46
|
+
@threads << t
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class UberThread < Thread
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
end
|
data/test/executor_spec.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'rspec'
|
2
2
|
|
3
|
-
require_relative '../lib/
|
3
|
+
require_relative '../lib/concur'
|
4
4
|
require_relative 'job'
|
5
5
|
|
6
6
|
describe Concur::Executor do
|
7
|
-
|
7
|
+
describe "#score" do
|
8
8
|
it "runs faster in parallel" do
|
9
|
+
|
10
|
+
job = Job.new(1)
|
11
|
+
puts 'runnable? ' + job.is_a?(Concur::Runnable).to_s
|
9
12
|
start_time = Time.now
|
10
13
|
20.times do |i|
|
11
14
|
job = Job.new(i)
|
@@ -14,24 +17,66 @@ describe Concur::Executor do
|
|
14
17
|
non_concurrent_duration = Time.now - start_time
|
15
18
|
puts "duration=" + non_concurrent_duration.to_s
|
16
19
|
|
17
|
-
puts
|
20
|
+
puts '---------------'
|
18
21
|
|
19
22
|
puts "Now for concurrent"
|
20
|
-
executor = Concur::Executor.new
|
23
|
+
executor = Concur::Executor.new # unbounded
|
21
24
|
start_time = Time.now
|
25
|
+
|
22
26
|
jobs = []
|
23
27
|
20.times do |i|
|
24
28
|
job = Job.new(i)
|
25
|
-
future = executor.
|
29
|
+
future = executor.execute(job)
|
26
30
|
jobs << future
|
27
31
|
end
|
28
32
|
jobs.each do |j|
|
29
33
|
puts "uber fast result=#{j.get}"
|
30
34
|
end
|
31
35
|
concurrent_duration = Time.now - start_time
|
32
|
-
puts "duration=" + concurrent_duration
|
36
|
+
puts "duration=" + concurrent_duration.to_s
|
37
|
+
|
38
|
+
concurrent_duration.should be < (non_concurrent_duration/2)
|
39
|
+
|
40
|
+
puts "Now for pooled"
|
41
|
+
executor = Concur::Executor.new_thread_pool_executor(:max_size=>10)
|
42
|
+
start_time = Time.now
|
43
|
+
|
44
|
+
jobs = []
|
45
|
+
20.times do |i|
|
46
|
+
job = Job.new(i)
|
47
|
+
future = executor.execute(job)
|
48
|
+
jobs << future
|
49
|
+
end
|
50
|
+
jobs.each do |j|
|
51
|
+
puts "uber fast result=#{j.get}"
|
52
|
+
end
|
53
|
+
pooled_duration = Time.now - start_time
|
54
|
+
puts "pooled_duration=" + pooled_duration.to_s
|
55
|
+
|
56
|
+
pooled_duration.should be < (non_concurrent_duration/2)
|
57
|
+
# pooled_duration.should_be > concurrent_duration
|
58
|
+
|
59
|
+
executor.shutdown
|
33
60
|
|
34
|
-
concurrent_duration.should be < (non_concurrent_duration-2)
|
35
61
|
end
|
36
62
|
end
|
37
63
|
end
|
64
|
+
|
65
|
+
describe Concur::Future do
|
66
|
+
|
67
|
+
describe "#new" do
|
68
|
+
|
69
|
+
it "can accept blocks" do
|
70
|
+
future = Concur::Future.new do
|
71
|
+
puts "i'm in the block"
|
72
|
+
"result of block"
|
73
|
+
end
|
74
|
+
puts 'get=' + future.get
|
75
|
+
future.get.should == "result of block"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
data/test/job.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concur
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,20 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-03-10 00:00:00.000000000 -08:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: &22290888 !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: *22290888
|
15
26
|
description: A concurrency library for Ruby like java.util.concurrency By http://www.appoxy.com
|
16
27
|
email: travis@appoxy.com
|
17
28
|
executables: []
|
@@ -23,6 +34,7 @@ files:
|
|
23
34
|
- lib/executor.rb
|
24
35
|
- lib/future.rb
|
25
36
|
- lib/runnable.rb
|
37
|
+
- lib/thread_pool.rb
|
26
38
|
- README.markdown
|
27
39
|
- test/executor_spec.rb
|
28
40
|
- test/job.rb
|
@@ -47,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
59
|
version: '0'
|
48
60
|
requirements: []
|
49
61
|
rubyforge_project:
|
50
|
-
rubygems_version: 1.
|
62
|
+
rubygems_version: 1.6.0
|
51
63
|
signing_key:
|
52
64
|
specification_version: 3
|
53
65
|
summary: A concurrency library for Ruby like java.util.concurrency. By http://www.appoxy.com
|