threadlimiter 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ 0.2.0 (11-02-2012)
2
+
3
+ * Block by default.
4
+
5
+ * Added option noblock.
6
+
7
+ * Added ThreadLimiter.open.
8
+
9
+ * Added ThreadLimiter#wait.
10
+
11
+ * Removed default limit.
12
+
13
+ * Removed default number_of_clusters.
14
+
15
+ * Fixed limit=-1 when calling several methods.
16
+
17
+ * Worked around a Ruby bug in Thread.fork(*args, &block).
18
+
1
19
  0.1.2 (06-10-2008)
2
20
 
3
21
  * Introduced ThreadLimiter.handle_clusters, which is reused by
data/README CHANGED
@@ -13,7 +13,7 @@ The traditional way, using Thread directly:
13
13
  titles =
14
14
  urls.collect do |url|
15
15
  Thread.fork do
16
- # ... get the title of the url...
16
+ # ...get the title of the url...
17
17
  end
18
18
  end.collect do |thread|
19
19
  thread.value
@@ -21,13 +21,15 @@ The traditional way, using Thread directly:
21
21
 
22
22
  With ThreadLimiter#fork():
23
23
 
24
- thread_limiter = ThreadLimiter.new(10) # Max. 10 concurrently running threads.
24
+ require "threadlimiter"
25
+
25
26
  urls = [.....] # A lot of URL's. Maybe even thousends.
27
+ thread_limiter = ThreadLimiter.new(10) # Max. 10 concurrently running threads.
26
28
 
27
29
  titles =
28
30
  urls.collect do |url|
29
31
  thread_limiter.fork do
30
- # ... get the title of the url...
32
+ # ...get the title of the url...
31
33
  end
32
34
  end.collect do |thread|
33
35
  thread.value
@@ -35,9 +37,11 @@ With ThreadLimiter#fork():
35
37
 
36
38
  With Enumerable#threaded_collect():
37
39
 
40
+ require "threadlimiter"
41
+
38
42
  urls = [.....] # A lot of URL's. Maybe even thousends.
39
43
 
40
44
  titles =
41
45
  urls.threaded_collect(10) do |url| # Max. 10 concurrently running threads.
42
- # ... get the title of the url...
46
+ # ...get the title of the url...
43
47
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -24,7 +24,7 @@ module Enumerable
24
24
  # Each cluster is run concurrently in a thread, using ThreadLimiter.new(<i>number_of_clusters</i>) and its fork().
25
25
  # Set <i>number_of_clusters</i> to -1 to skip clustering.
26
26
 
27
- def clustered_threaded_collect(number_of_clusters=-1, &block)
27
+ def clustered_threaded_collect(number_of_clusters, &block)
28
28
  if number_of_clusters <= 0
29
29
  threaded_collect(number_of_clusters, &block)
30
30
  else
@@ -39,7 +39,7 @@ module Enumerable
39
39
  if limit == 0
40
40
  self.select(&block)
41
41
  else
42
- self.zip(self.threaded_collect(limit=-1, &block)).inject([]){|r, (o, b)| r << o if b ; r}
42
+ self.zip(self.threaded_collect(limit, &block)).inject([]){|r, (o, b)| r << o if b ; r}
43
43
  end
44
44
  end
45
45
 
@@ -50,7 +50,7 @@ module Enumerable
50
50
  if limit == 0
51
51
  self.reject(&block)
52
52
  else
53
- self.zip(self.threaded_collect(limit=-1, &block)).inject([]){|r, (o, b)| r << o unless b ; r}
53
+ self.zip(self.threaded_collect(limit, &block)).inject([]){|r, (o, b)| r << o unless b ; r}
54
54
  end
55
55
  end
56
56
 
@@ -61,7 +61,7 @@ module Enumerable
61
61
  if limit == 0
62
62
  self.each(&block)
63
63
  else
64
- threaded_collect(limit=-1, &block)
64
+ threaded_collect(limit, &block)
65
65
 
66
66
  self
67
67
  end
@@ -71,24 +71,24 @@ module Enumerable
71
71
  # Each cluster is run concurrently in a thread, using ThreadLimiter.new(<i>number_of_clusters</i>) and its fork().
72
72
  # Set <i>number_of_clusters</i> to -1 to skip clustering.
73
73
 
74
- def clustered_threaded_select(number_of_clusters=-1, &block)
75
- self.zip(self.clustered_threaded_collect(number_of_clusters=-1, &block)).inject([]){|r, (o, b)| r << o if b ; r}
74
+ def clustered_threaded_select(number_of_clusters, &block)
75
+ self.zip(self.clustered_threaded_collect(number_of_clusters, &block)).inject([]){|r, (o, b)| r << o if b ; r}
76
76
  end
77
77
 
78
78
  # Like Enumerable#reject(), but all blocks are clustered.
79
79
  # Each cluster is run concurrently in a thread, using ThreadLimiter.new(<i>number_of_clusters</i>) and its fork().
80
80
  # Set <i>number_of_clusters</i> to -1 to skip clustering.
81
81
 
82
- def clustered_threaded_reject(number_of_clusters=-1, &block)
83
- self.zip(self.clustered_threaded_collect(number_of_clusters=-1, &block)).inject([]){|r, (o, b)| r << o unless b ; r}
82
+ def clustered_threaded_reject(number_of_clusters, &block)
83
+ self.zip(self.clustered_threaded_collect(number_of_clusters, &block)).inject([]){|r, (o, b)| r << o unless b ; r}
84
84
  end
85
85
 
86
86
  # Like Enumerable#each(), but all blocks are clustered.
87
87
  # Each cluster is run concurrently in a thread, using ThreadLimiter.new(<i>number_of_clusters</i>) and its fork().
88
88
  # Set <i>number_of_clusters</i> to -1 to skip clustering.
89
89
 
90
- def clustered_threaded_each(number_of_clusters=-1, &block)
91
- clustered_threaded_collect(number_of_clusters=-1, &block)
90
+ def clustered_threaded_each(number_of_clusters, &block)
91
+ clustered_threaded_collect(number_of_clusters, &block)
92
92
 
93
93
  self
94
94
  end
@@ -4,13 +4,27 @@
4
4
  # ThreadLimiter isn't a thread pool. Each fork really starts a new thread.
5
5
 
6
6
  class ThreadLimiter
7
+ # Create and use a new ThreadLimiter and wait for all threads to finish.
8
+
9
+ def self.open(*args)
10
+ thread_limiter = new(*args)
11
+
12
+ begin
13
+ yield(thread_limiter)
14
+ ensure
15
+ thread_limiter.wait
16
+ end
17
+ end
18
+
7
19
  # Initialize the ThreadLimiter.
8
20
  # The optional parameter <i>limit</i> is the maximum number of concurrently running threads.
9
21
  # Set <i>limit</i> to -1 or 0 to fork threads without limiting the number of concurrently running threads.
22
+ # Set options[:noblock] to true to start the new thread before waiting for resources.
10
23
 
11
- def initialize(limit=-1)
24
+ def initialize(limit, options={})
12
25
  @limit = limit # The maximum number of concurrently running threads.
13
26
  @running = 0 # The number of currently running threads.
27
+ @noblock = options[:noblock]
14
28
 
15
29
  @mutex = Mutex.new
16
30
  @cv = ConditionVariable.new
@@ -24,36 +38,28 @@ class ThreadLimiter
24
38
 
25
39
  def fork(*args, &block)
26
40
  if @limit <= 0
27
- Thread.fork(*args, &block)
28
- else
29
- @mutex.synchronize do
30
- while @running >= @limit
31
- @cv.wait(@mutex)
32
- end
33
-
34
- @running += 1
41
+ Thread.fork do
42
+ yield(*args)
35
43
  end
44
+ else
45
+ cv_wait unless @noblock
36
46
 
37
47
  Thread.fork do
48
+ cv_wait if @noblock
49
+
38
50
  begin
39
- res = yield(*args)
51
+ yield(*args)
40
52
  ensure
41
- @mutex.synchronize do
42
- @running -= 1
43
- end
44
-
45
- @cv.signal if @limit > 0
53
+ cv_signal
46
54
  end
47
-
48
- res
49
55
  end
50
56
  end
51
57
  end
52
58
 
53
- def self.handle_clusters(enumeration, number_of_clusters, method_name, &block)
59
+ def self.handle_clusters(enumeration, number_of_clusters, method_name, &block) # :nodoc:
54
60
  clusters = [] # One cluster per fork.
55
61
  last_pos = -1
56
- res = []
62
+ res = []
57
63
 
58
64
  enumeration.each do |object|
59
65
  last_pos += 1
@@ -78,4 +84,39 @@ class ThreadLimiter
78
84
  res[0..last_pos] # Remove padding nil.
79
85
  end
80
86
 
87
+ # Wait for all threads to finish.
88
+
89
+ def wait
90
+ @mutex.synchronize do
91
+ while @running > 0
92
+ @cv.wait(@mutex)
93
+ end
94
+ end
95
+
96
+ self
97
+ end
98
+
99
+ private
100
+
101
+ def cv_wait
102
+ @mutex.synchronize do
103
+ while @running >= @limit
104
+ @cv.wait(@mutex)
105
+ end
106
+
107
+ @running += 1
108
+ end
109
+
110
+ self
111
+ end
112
+
113
+ def cv_signal
114
+ @mutex.synchronize do
115
+ @running -= 1
116
+
117
+ @cv.signal
118
+ end
119
+
120
+ self
121
+ end
81
122
  end
@@ -0,0 +1,3 @@
1
+ class ThreadLimiter
2
+ VERSION = "0.2.0"
3
+ end
data/lib/threadlimiter.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "thread"
2
2
 
3
+ require "threadlimiter/version"
3
4
  require "threadlimiter/threadlimiter"
4
5
  require "threadlimiter/enumerable"
data/test/test.rb CHANGED
@@ -2,9 +2,9 @@ require "test/unit"
2
2
  require "threadlimiter"
3
3
 
4
4
  class ThreadLimiterTest < Test::Unit::TestCase
5
- def go(limit)
5
+ def go(limit, options={})
6
6
  input = (1..100).collect{rand}
7
- threadlimiter = ThreadLimiter.new(limit)
7
+ threadlimiter = ThreadLimiter.new(limit, options)
8
8
 
9
9
  threads =
10
10
  input.collect do |m|
@@ -17,7 +17,6 @@ class ThreadLimiterTest < Test::Unit::TestCase
17
17
 
18
18
  assert_equal([Thread], threads.collect{|t| t.class}.uniq)
19
19
 
20
-
21
20
  assert_equal(input.to_a , threads.collect{|t| t.value})
22
21
  assert_equal(0 , threadlimiter.instance_eval{@running})
23
22
  assert_equal(limit , threadlimiter.instance_eval{@limit})
@@ -34,6 +33,18 @@ class ThreadLimiterTest < Test::Unit::TestCase
34
33
  def test_with_zero_limit
35
34
  go(0)
36
35
  end
36
+
37
+ def test_with_limit_with_noblock
38
+ go(10, :noblock=>true)
39
+ end
40
+
41
+ def test_with_no_limit_with_noblock
42
+ go(-1, :noblock=>true)
43
+ end
44
+
45
+ def test_with_zero_limit_with_noblock
46
+ go(0, :noblock=>true)
47
+ end
37
48
  end
38
49
 
39
50
  class ThreadLimiterEnumerableTest < Test::Unit::TestCase
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: threadlimiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Erik Veenstra
@@ -9,8 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2008-10-06 00:00:00 +02:00
13
- default_executable:
18
+ date: 2012-02-11 00:00:00 Z
14
19
  dependencies: []
15
20
 
16
21
  description: Fork threads like Thread.fork, but limit the number of concurrently running threads.
@@ -22,16 +27,18 @@ extensions: []
22
27
  extra_rdoc_files: []
23
28
 
24
29
  files:
25
- - lib/threadlimiter
26
- - lib/threadlimiter/threadlimiter.rb
27
- - lib/threadlimiter/enumerable.rb
28
30
  - lib/threadlimiter.rb
31
+ - lib/threadlimiter/enumerable.rb
32
+ - lib/threadlimiter/threadlimiter.rb
33
+ - lib/threadlimiter/version.rb
29
34
  - README
30
35
  - LICENSE
31
36
  - VERSION
32
37
  - CHANGELOG
33
- has_rdoc: true
38
+ - test/test.rb
34
39
  homepage: http://www.erikveen.dds.nl/threadlimiter/index.html
40
+ licenses: []
41
+
35
42
  post_install_message:
36
43
  rdoc_options:
37
44
  - README
@@ -39,29 +46,35 @@ rdoc_options:
39
46
  - VERSION
40
47
  - CHANGELOG
41
48
  - --title
42
- - threadlimiter (0.1.2)
49
+ - threadlimiter (0.2.0)
43
50
  - --main
44
51
  - README
45
52
  require_paths:
46
53
  - lib
47
54
  required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
48
56
  requirements:
49
57
  - - ">="
50
58
  - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
51
62
  version: "0"
52
- version:
53
63
  required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
54
65
  requirements:
55
66
  - - ">="
56
67
  - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
57
71
  version: "0"
58
- version:
59
72
  requirements: []
60
73
 
61
74
  rubyforge_project: threadlimiter
62
- rubygems_version: 1.2.0
75
+ rubygems_version: 1.8.12
63
76
  signing_key:
64
- specification_version: 2
77
+ specification_version: 3
65
78
  summary: Fork threads like Thread.fork, but limit the number of concurrently running threads.
66
79
  test_files:
67
80
  - test/test.rb