seekingalpha_thread 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +229 -0
- data/Rakefile +6 -0
- data/lib/thread/channel.rb +109 -0
- data/lib/thread/delay.rb +94 -0
- data/lib/thread/every.rb +197 -0
- data/lib/thread/future.rb +164 -0
- data/lib/thread/pipe.rb +119 -0
- data/lib/thread/pool.rb +488 -0
- data/lib/thread/process.rb +71 -0
- data/lib/thread/promise.rb +83 -0
- data/lib/thread/recursive_mutex.rb +38 -0
- data/spec/thread/channel_spec.rb +39 -0
- data/spec/thread/delay_spec.rb +11 -0
- data/spec/thread/every_spec.rb +9 -0
- data/spec/thread/future_spec.rb +34 -0
- data/spec/thread/pipe_spec.rb +23 -0
- data/spec/thread/pool_spec.rb +86 -0
- data/spec/thread/promise_spec.rb +35 -0
- data/thread.gemspec +22 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 34ba9568e012ba79315091c9d17d3ddca949a78a
|
4
|
+
data.tar.gz: 45a64a3d8f0979fac3713da7970aafc40af9e752
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90170a1870d4be263ac93a69526334951201c7cedc52dd8e91a37cdaeab0859429715d45f9bdddf6fb3f6bbc41f4cb39c192279870acfb5529db4f2bc3b18c52
|
7
|
+
data.tar.gz: 9d01b958cf27490c341ec917e67691ab2bb3813866981a96fc47b4ba04268570098c9d30077a3562e5f819c13c3f83cd772e19325fe7a9c975b3b5199093502b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
data/README.md
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# thread
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/meh/ruby-thread.svg?branch=master)](https://travis-ci.org/meh/ruby-thread)
|
4
|
+
|
5
|
+
Various extensions to the thread library in ruby.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'thread'
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install thread
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Pool
|
20
|
+
|
21
|
+
All the implementations I looked at were either buggy or wasted CPU resources
|
22
|
+
for no apparent reason, for example used a sleep of 0.01 seconds to then check for
|
23
|
+
readiness and stuff like this.
|
24
|
+
|
25
|
+
This implementation uses standard locking functions to work properly across multiple Ruby
|
26
|
+
implementations.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'thread/pool'
|
30
|
+
|
31
|
+
pool = Thread.pool(4)
|
32
|
+
|
33
|
+
10.times {
|
34
|
+
pool.process {
|
35
|
+
sleep 2
|
36
|
+
|
37
|
+
puts 'lol'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
pool.shutdown
|
42
|
+
```
|
43
|
+
|
44
|
+
You should get 4 lols every 2 seconds and it should exit after 10 of them.
|
45
|
+
|
46
|
+
### Channel
|
47
|
+
|
48
|
+
This implements a channel where you can write messages and receive messages.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'thread/channel'
|
52
|
+
|
53
|
+
channel = Thread.channel
|
54
|
+
channel.send 'wat'
|
55
|
+
channel.receive # => 'wat'
|
56
|
+
|
57
|
+
channel = Thread.channel { |o| o.is_a?(Integer) }
|
58
|
+
channel.send 'wat' # => ArgumentError: guard mismatch
|
59
|
+
|
60
|
+
Thread.new {
|
61
|
+
while num = channel.receive(&:even?)
|
62
|
+
puts 'Aye!'
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
Thread.new {
|
67
|
+
while num = channel.receive(&:odd?)
|
68
|
+
puts 'Arrr!'
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
loop {
|
73
|
+
channel.send rand(1_000_000_000)
|
74
|
+
|
75
|
+
sleep 0.5
|
76
|
+
}
|
77
|
+
```
|
78
|
+
|
79
|
+
### Pipe
|
80
|
+
|
81
|
+
A pipe allows you to execute various tasks on a set of data in parallel,
|
82
|
+
each datum inserted in the pipe is passed along through queues to the various
|
83
|
+
functions composing the pipe, the final result is inserted in the final queue.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
require 'thread/pipe'
|
87
|
+
|
88
|
+
p = Thread |-> d { d * 2 } |-> d { d * 4 }
|
89
|
+
p << 2
|
90
|
+
|
91
|
+
puts ~p # => 16
|
92
|
+
```
|
93
|
+
|
94
|
+
### Process
|
95
|
+
|
96
|
+
A process helps reducing programming errors coming from race conditions and the
|
97
|
+
like, the only way to interact with a process is through messages.
|
98
|
+
|
99
|
+
Multiple processes should talk with eachother through messages.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
require 'thread/process'
|
103
|
+
|
104
|
+
p = Thread.process {
|
105
|
+
loop {
|
106
|
+
puts receive.inspect
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
p << 42
|
111
|
+
p << 23
|
112
|
+
```
|
113
|
+
|
114
|
+
### Promise
|
115
|
+
|
116
|
+
This implements the promise pattern, allowing you to pass around an object
|
117
|
+
where you can send a value and extract a value, in a thread-safe way, accessing
|
118
|
+
the value will wait for the value to be delivered.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'thread/promise'
|
122
|
+
|
123
|
+
p = Thread.promise
|
124
|
+
|
125
|
+
Thread.new {
|
126
|
+
sleep 5
|
127
|
+
p << 42
|
128
|
+
}
|
129
|
+
|
130
|
+
puts ~p # => 42
|
131
|
+
```
|
132
|
+
|
133
|
+
### Future
|
134
|
+
|
135
|
+
A future is somewhat a promise, except you pass it a block to execute in
|
136
|
+
another thread.
|
137
|
+
|
138
|
+
The value returned by the block will be the value of the promise.
|
139
|
+
|
140
|
+
By default, `Thread.future` executes the block in a newly-created thread.
|
141
|
+
|
142
|
+
`Thread.future` accepts an optional argument of type `Thread.pool` if you want
|
143
|
+
the block executed in an existing thread-pool.
|
144
|
+
|
145
|
+
You can also use the `Thread::Pool` helper `#future`
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
require 'thread/future'
|
149
|
+
|
150
|
+
f = Thread.future {
|
151
|
+
sleep 5
|
152
|
+
|
153
|
+
42
|
154
|
+
}
|
155
|
+
|
156
|
+
puts ~f # => 42
|
157
|
+
```
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
require 'thread/pool'
|
161
|
+
require 'thread/future'
|
162
|
+
|
163
|
+
pool = Thread.pool 4
|
164
|
+
f = Thread.future pool do
|
165
|
+
sleep 5
|
166
|
+
42
|
167
|
+
end
|
168
|
+
|
169
|
+
puts ~f # => 42
|
170
|
+
```
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
require 'thread/pool'
|
174
|
+
require 'thread/future'
|
175
|
+
|
176
|
+
pool = Thread.pool 4
|
177
|
+
f = pool.future {
|
178
|
+
sleep 5
|
179
|
+
42
|
180
|
+
}
|
181
|
+
|
182
|
+
puts ~f # => 42
|
183
|
+
```
|
184
|
+
|
185
|
+
|
186
|
+
### Delay
|
187
|
+
|
188
|
+
A delay is kind of a promise, except the block is called when the value is
|
189
|
+
being accessed and the result is cached.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
require 'thread/delay'
|
193
|
+
|
194
|
+
d = Thread.delay {
|
195
|
+
42
|
196
|
+
}
|
197
|
+
|
198
|
+
puts ~d # => 42
|
199
|
+
```
|
200
|
+
|
201
|
+
### Every
|
202
|
+
|
203
|
+
An every executes the block every given seconds and yields the value to the
|
204
|
+
every object, you can then check if the current value is old or how much time
|
205
|
+
is left until the second call is done.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
require 'net/http'
|
209
|
+
require 'thread/every'
|
210
|
+
|
211
|
+
e = Thread.every(5) {
|
212
|
+
Net::HTTP.get(URI.parse('http://www.whattimeisit.com/')).match %r{<B>(.*?)<BR>\s+(.*?)</B>}m do |m|
|
213
|
+
{ date: m[1], time: m[2] }
|
214
|
+
end
|
215
|
+
}
|
216
|
+
|
217
|
+
loop do
|
218
|
+
puts ~e
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
## Contributing
|
223
|
+
|
224
|
+
1. Fork it ( https://github.com/meh/ruby-thread/fork )
|
225
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
226
|
+
3. Verify new and old specs are green (`rake`)
|
227
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
228
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
229
|
+
6. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
# A channel lets you send and receive various messages in a thread-safe way.
|
14
|
+
#
|
15
|
+
# It also allows for guards upon sending and retrieval, to ensure the passed
|
16
|
+
# messages are safe to be consumed.
|
17
|
+
class Thread::Channel
|
18
|
+
# Create a channel with optional initial messages and optional channel guard.
|
19
|
+
def initialize(messages = [], &block)
|
20
|
+
@messages = []
|
21
|
+
@mutex = Mutex.new
|
22
|
+
@check = block
|
23
|
+
|
24
|
+
messages.each {|o|
|
25
|
+
send o
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Send a message to the channel.
|
30
|
+
#
|
31
|
+
# If there's a guard, the value is passed to it, if the guard returns a falsy value
|
32
|
+
# an ArgumentError exception is raised and the message is not sent.
|
33
|
+
def send(what)
|
34
|
+
if @check && !@check.call(what)
|
35
|
+
raise ArgumentError, 'guard mismatch'
|
36
|
+
end
|
37
|
+
|
38
|
+
@mutex.synchronize {
|
39
|
+
@messages << what
|
40
|
+
|
41
|
+
cond.broadcast if cond?
|
42
|
+
}
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Receive a message, if there are none the call blocks until there's one.
|
48
|
+
#
|
49
|
+
# If a block is passed, it's used as guard to match to a message.
|
50
|
+
def receive(&block)
|
51
|
+
message = nil
|
52
|
+
found = false
|
53
|
+
|
54
|
+
if block
|
55
|
+
until found
|
56
|
+
@mutex.synchronize {
|
57
|
+
if index = @messages.find_index(&block)
|
58
|
+
message = @messages.delete_at(index)
|
59
|
+
found = true
|
60
|
+
else
|
61
|
+
cond.wait @mutex
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
else
|
66
|
+
until found
|
67
|
+
@mutex.synchronize {
|
68
|
+
if @messages.empty?
|
69
|
+
cond.wait @mutex
|
70
|
+
end
|
71
|
+
|
72
|
+
unless @messages.empty?
|
73
|
+
message = @messages.shift
|
74
|
+
found = true
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
message
|
81
|
+
end
|
82
|
+
|
83
|
+
# Receive a message, if there are none the call returns nil.
|
84
|
+
#
|
85
|
+
# If a block is passed, it's used as guard to match to a message.
|
86
|
+
def receive!(&block)
|
87
|
+
if block
|
88
|
+
@messages.delete_at(@messages.find_index(&block))
|
89
|
+
else
|
90
|
+
@messages.shift
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def cond?
|
96
|
+
instance_variable_defined? :@cond
|
97
|
+
end
|
98
|
+
|
99
|
+
def cond
|
100
|
+
@cond ||= ConditionVariable.new
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Thread
|
105
|
+
# Helper to create a channel.
|
106
|
+
def self.channel(*args, &block)
|
107
|
+
Thread::Channel.new(*args, &block)
|
108
|
+
end
|
109
|
+
end
|
data/lib/thread/delay.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
|
13
|
+
# A delay is an object that incapsulates a block which is called upon
|
14
|
+
# value retrieval, and its result cached.
|
15
|
+
class Thread::Delay
|
16
|
+
# Create a delay with the passed block.
|
17
|
+
def initialize (&block)
|
18
|
+
raise ArgumentError, 'no block given' unless block
|
19
|
+
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check if an exception has been raised.
|
25
|
+
def exception?
|
26
|
+
@mutex.synchronize {
|
27
|
+
instance_variable_defined? :@exception
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the raised exception.
|
32
|
+
def exception
|
33
|
+
@mutex.synchronize {
|
34
|
+
@exception
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if the delay has been called.
|
39
|
+
def delivered?
|
40
|
+
@mutex.synchronize {
|
41
|
+
instance_variable_defined? :@value
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
alias realized? delivered?
|
46
|
+
|
47
|
+
# Get the value of the delay, if it's already been executed, return the
|
48
|
+
# cached result, otherwise execute the block and return the value.
|
49
|
+
#
|
50
|
+
# In case the block raises an exception, it will be raised, the exception is
|
51
|
+
# cached and will be raised every time you access the value.
|
52
|
+
def value
|
53
|
+
@mutex.synchronize {
|
54
|
+
raise @exception if instance_variable_defined? :@exception
|
55
|
+
|
56
|
+
return @value if instance_variable_defined? :@value
|
57
|
+
|
58
|
+
begin
|
59
|
+
@value = @block.call
|
60
|
+
rescue Exception => e
|
61
|
+
@exception = e
|
62
|
+
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
alias ~ value
|
69
|
+
|
70
|
+
# Do the same as {#value}, but return nil in case of exception.
|
71
|
+
def value!
|
72
|
+
begin
|
73
|
+
value
|
74
|
+
rescue Exception
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
alias ! value!
|
80
|
+
end
|
81
|
+
|
82
|
+
class Thread
|
83
|
+
# Helper to create Thread::Delay
|
84
|
+
def self.delay (&block)
|
85
|
+
Thread::Delay.new(&block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module Kernel
|
90
|
+
# Helper to create a Thread::Delay
|
91
|
+
def delay (&block)
|
92
|
+
Thread::Delay.new(&block)
|
93
|
+
end
|
94
|
+
end
|