async_enum 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +35 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/README.md +121 -0
- data/Rakefile +9 -0
- data/async_enum.gemspec +21 -0
- data/demos/lock.rb +9 -0
- data/demos/pool_size.rb +12 -0
- data/demos/sleep.rb +12 -0
- data/demos/sync.rb +4 -0
- data/lib/async_enum.rb +8 -0
- data/lib/enumerator/async.rb +106 -0
- data/test/async_enum_test/benchmark_test.rb +12 -0
- data/test/async_enum_test/definitions_test.rb +41 -0
- data/test/async_enum_test/enumerator_async_test.rb +101 -0
- data/test/async_enum_test/thread_pool_test.rb +24 -0
- data/test/test_helper.rb +35 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWU0MWNjY2UwYTE3OTI5Njc1OThiZTAyMDNjMjliNmQ3MWMzZmZjMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGM5ZDA0NjA5NTI3M2YyNjVjZTA1N2UxOTVmYzI5MGQ2OGQ5YzhlNg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjQ0Nzc5NmVlNTQzMzMzZmEyNzBmOTU4ZThkYWZmOWYwYmFmYTUxMDkxMTU5
|
10
|
+
NTFhNzI0MTUxNDQ2NGIyZjU3OTAyMTFiZTFiZjdmZjNlYWNhMGVlYjdkNTY2
|
11
|
+
OGFmZWM4OTk4MzUyNGUwYTQ3YWQ1N2Q3YjZjYWU2NjFkMmNlNTc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MWQyYWM1ZWQ1ODc1YjQ0M2M2Y2VhMjQzM2I5ODhiODU0MjEzOTdmNzM0ZTY2
|
14
|
+
ZDcwMTE5YjhlYTEwZmUwMDBlMmE5MTI0MWY4MGU3ZWNlOTMxMzlhN2ZhOWIy
|
15
|
+
M2ExYjE1ZWI2MjM0MjBkMGM3ZTY1YWFiNDhjNmIwNmNmZWYzOTk=
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#********** osx template**********
|
2
|
+
|
3
|
+
.DS_Store
|
4
|
+
|
5
|
+
# Thumbnails
|
6
|
+
._*
|
7
|
+
|
8
|
+
# Files that might appear on external disk
|
9
|
+
.Spotlight-V100
|
10
|
+
.Trashes
|
11
|
+
|
12
|
+
|
13
|
+
#********** ruby template**********
|
14
|
+
|
15
|
+
*.gem
|
16
|
+
Gemfile.lock
|
17
|
+
*.rbc
|
18
|
+
.bundle
|
19
|
+
.config
|
20
|
+
coverage
|
21
|
+
InstalledFiles
|
22
|
+
lib/bundler/man
|
23
|
+
pkg
|
24
|
+
rdoc
|
25
|
+
spec/reports
|
26
|
+
test/tmp
|
27
|
+
test/version_tmp
|
28
|
+
tmp
|
29
|
+
bin/
|
30
|
+
|
31
|
+
# YARD artifacts
|
32
|
+
.yardoc
|
33
|
+
_yardoc
|
34
|
+
doc/
|
35
|
+
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# async\_enum
|
2
|
+
|
3
|
+
Inspired by Enumerable#lazy coupled with Enumerator::Lazy, and the Async.js library, I thought it would be cool to have syntax like `urls.async.map{ |url| http_get(url) }`.
|
4
|
+
|
5
|
+
![](https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-prn1/554187_10151609082562269_1115589261_n.jpg)
|
6
|
+
|
7
|
+
#### Runs In Parallel
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'benchmark'
|
11
|
+
|
12
|
+
sync = Benchmark.measure do
|
13
|
+
5.times.each{ sleep 0.1 }
|
14
|
+
end
|
15
|
+
|
16
|
+
async = Benchmark.measure do
|
17
|
+
5.times.async.each{ sleep 0.1 }
|
18
|
+
end
|
19
|
+
|
20
|
+
puts sync, async
|
21
|
+
# 0.000000 0.000000 0.000000 ( 0.500782)
|
22
|
+
# 0.000000 0.010000 0.010000 ( 0.102763)
|
23
|
+
```
|
24
|
+
|
25
|
+
#### How It Works
|
26
|
+
|
27
|
+
The implementation was based on `Enumerable#lazy` introduced with Ruby 2.0. `Enumerator::Async` follows a similar approach, where the Enumerator is passed into the constructor.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
enum = ('a'..'z').each
|
31
|
+
|
32
|
+
# the following are equivalent
|
33
|
+
|
34
|
+
Enumerator::Async.new(enum)
|
35
|
+
enum.async
|
36
|
+
```
|
37
|
+
|
38
|
+
To get the enumerator back from the async enumerator, simply call `sync` or `to_enum` like so:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
enum.async.sync == enum
|
42
|
+
# => true
|
43
|
+
```
|
44
|
+
|
45
|
+
Every operation on the async enumerator affects the contained enumerator in the same way. For instance:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
enum.with_index.to_a
|
49
|
+
# => [ ['a', 0], ['b', 1], ['c', 2] ... ]
|
50
|
+
|
51
|
+
enum.async.with_index.to_a
|
52
|
+
# => [ ['a', 0], ['b', 1], ['c', 2] ... ]
|
53
|
+
```
|
54
|
+
|
55
|
+
Async methods can be chained just like tipical enumerator methods:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
enum.async.each{ sleep(0.1) }.each{ sleep(0.1) }
|
59
|
+
```
|
60
|
+
|
61
|
+
#### How to use it
|
62
|
+
|
63
|
+
The method `Enumerable#async` was added so that every collection can be processed in parallel:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
[ 0, 1, 2 ].async.each{ |i| puts i }
|
67
|
+
(0..2).async.map(&:to_i)
|
68
|
+
|
69
|
+
# or chain to your heart's content
|
70
|
+
|
71
|
+
(0..5).reject(&:even?).reverse.async.with_index.map{ |x, index| x + index }
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Limiting thread pool size
|
75
|
+
|
76
|
+
To limit the thread pool size, you can pass in an optional parameter to `async`. Suppose for performance reasons you want to use a maximum of 4 threads:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
(0..100).async(4).each do
|
80
|
+
# use bandwith
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
#### Preventing race conditions
|
85
|
+
|
86
|
+
When programming concurrently, nasty bugs can come up because some operations aren't atomic. For instance, incrementing a variable `x += 1` will not necessarily work as expected. To provide easy locking, there's a DSL-style `lock` method you can use in the block passed to the async enum.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
count = 0
|
90
|
+
('a'..'z').async.each do
|
91
|
+
lock :count do
|
92
|
+
count += 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
count
|
96
|
+
# => 26
|
97
|
+
```
|
98
|
+
|
99
|
+
The name of the lock doesn't matter, but using the variable name helps make the code understandable. You should try to use 1 lock per thread-unsafe variable.
|
100
|
+
|
101
|
+
## Notes
|
102
|
+
|
103
|
+
To install it, add it to your gemfile:
|
104
|
+
|
105
|
+
```
|
106
|
+
# Gemfile
|
107
|
+
|
108
|
+
gem 'async_enum', github: 'aj0strow/async_enum'
|
109
|
+
```
|
110
|
+
|
111
|
+
To run the demos, clone the project and run with the library included in the load path:
|
112
|
+
|
113
|
+
```
|
114
|
+
$ git clone git@github.com:aj0strow/async_enum
|
115
|
+
$ cd async_enum
|
116
|
+
$ ruby -I lib demos/sleep.rb
|
117
|
+
```
|
118
|
+
|
119
|
+
**Disclaimer**: I am not an expert at multithreading. Quite the opposite in fact.
|
120
|
+
|
121
|
+
Please report errors and feel free to contribute and improve things!
|
data/Rakefile
ADDED
data/async_enum.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'async_enum'
|
7
|
+
s.version = '0.0.4'
|
8
|
+
s.authors = %w(aj0strow)
|
9
|
+
s.email = 'alexander.ostrow@gmail.com'
|
10
|
+
s.description = 'iterate over enumerable objects concurrently'
|
11
|
+
s.summary = 'Async Enumerable and Enumerator'
|
12
|
+
s.homepage = 'http://github.com/aj0strow/async_enum'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.test_files = s.files.grep(/test/)
|
17
|
+
s.require_paths = %w(lib)
|
18
|
+
|
19
|
+
s.add_development_dependency 'bundler'
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
end
|
data/demos/lock.rb
ADDED
data/demos/pool_size.rb
ADDED
data/demos/sleep.rb
ADDED
data/demos/sync.rb
ADDED
data/lib/async_enum.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
class Enumerator
|
2
|
+
class Async < Enumerator
|
3
|
+
|
4
|
+
class Lockset
|
5
|
+
def initialize
|
6
|
+
@semaphores = Hash.new do |locks, key|
|
7
|
+
locks[key] = Mutex.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def lock(key = :__default__, &thread_unsafe_block)
|
12
|
+
@semaphores[key].synchronize(&thread_unsafe_block)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :evaluate, :instance_exec
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(enum, pool_size = nil)
|
19
|
+
@enum = enum
|
20
|
+
@pool_size = pool_size
|
21
|
+
@lockset = Lockset.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_a
|
25
|
+
@enum.to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
def sync
|
29
|
+
@enum
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :to_enum, :sync
|
33
|
+
|
34
|
+
def size
|
35
|
+
@enum.size
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&work)
|
39
|
+
raise_error('each') unless block_given?
|
40
|
+
|
41
|
+
if @pool_size
|
42
|
+
threads = @pool_size.times.map do
|
43
|
+
Thread.new do
|
44
|
+
loop do
|
45
|
+
@enum.any? ? evaluate(*@enum.next, &work) : break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
threads.each(&:join)
|
50
|
+
@enum.rewind
|
51
|
+
else
|
52
|
+
unlimited_threads(&work).each(&:join)
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_index(start = 0, &work)
|
58
|
+
@enum = @enum.with_index(start)
|
59
|
+
if block_given?
|
60
|
+
each(&work)
|
61
|
+
else
|
62
|
+
self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_object(obj, &work)
|
67
|
+
@enum = @enum.with_object(obj)
|
68
|
+
if block_given?
|
69
|
+
each(&work); obj
|
70
|
+
else
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def map(&work)
|
76
|
+
raise_error('map') unless block_given?
|
77
|
+
|
78
|
+
if @pool_size
|
79
|
+
outs = []
|
80
|
+
with_index do |item, index|
|
81
|
+
outs[index] = evaluate(item, &work)
|
82
|
+
end
|
83
|
+
outs
|
84
|
+
else
|
85
|
+
unlimited_threads(&work).map(&:value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def evaluate(*args, &work)
|
92
|
+
@lockset.instance_exec(*args, &work)
|
93
|
+
end
|
94
|
+
|
95
|
+
def unlimited_threads(&work)
|
96
|
+
@enum.map do |*args|
|
97
|
+
Thread.new{ evaluate(*args, &work) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def raise_error(method)
|
102
|
+
raise ArgumentError, "tried to call async #{method} without a block"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DefinitionsTest < Test
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@methods = Enumerator::Async.instance_methods(false)
|
7
|
+
end
|
8
|
+
|
9
|
+
test 'enumerator async exists' do
|
10
|
+
assert_equal 'constant', defined?(Enumerator::Async)
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'enumerator async inherits from enumerator' do
|
14
|
+
assert_includes Enumerator::Async.ancestors, Enumerator
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'enumerator holds onto enum' do
|
18
|
+
enum = Enumerator::Async.new(1..5)
|
19
|
+
assert_equal (1..5).to_a, enum.instance_variable_get('@enum').to_a
|
20
|
+
end
|
21
|
+
|
22
|
+
test 'enumerator holds onto pool_size' do
|
23
|
+
enum = Enumerator::Async.new(1..5, 5)
|
24
|
+
assert_equal 5, enum.instance_variable_get('@pool_size')
|
25
|
+
end
|
26
|
+
|
27
|
+
%w(to_a to_enum sync each map with_index with_object).each do |method|
|
28
|
+
test "enumerator async responds to #{method}" do
|
29
|
+
assert_includes @methods, method.to_sym
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'enumerable#async exists' do
|
34
|
+
assert_includes Enumerable.instance_methods(false), :async
|
35
|
+
assert 5.times.respond_to?(:async)
|
36
|
+
assert (1..5).respond_to?(:async)
|
37
|
+
assert [].respond_to?(:async)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EnumeratorAsyncTest < Test
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@enum = Enumerator::Async.new( (1..5).each )
|
7
|
+
end
|
8
|
+
|
9
|
+
test 'to_a' do
|
10
|
+
assert_equal [1, 2, 3, 4, 5], @enum.to_a
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'sync returns enumerator' do
|
14
|
+
refute @enum.sync.is_a?(Enumerator::Async)
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'to_enum alias of sync' do
|
18
|
+
assert_equal @enum.sync, @enum.to_enum
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'size' do
|
22
|
+
if Enumerator.instance_methods(false).include?(:size)
|
23
|
+
assert_equal 5, @enum.size
|
24
|
+
else
|
25
|
+
assert_raises(NoMethodError) { @enum.size }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'each with no block' do
|
30
|
+
assert_raises(ArgumentError) { @enum.each }
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'each' do
|
34
|
+
nums = []
|
35
|
+
@enum.each do |i|
|
36
|
+
nums << i
|
37
|
+
end
|
38
|
+
assert_equal @enum.to_a, nums.sort
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'each with splatting' do
|
42
|
+
ranges = [ ('a'..'c').to_a ] * 3
|
43
|
+
enum = Enumerator::Async.new( ranges.each )
|
44
|
+
strs = []
|
45
|
+
enum.each do |a, b, c|
|
46
|
+
strs << (a + b + c)
|
47
|
+
end
|
48
|
+
assert_equal %w(abc abc abc), strs
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'with_index no block' do
|
52
|
+
pairs = @enum.with_index.to_a[0, 3]
|
53
|
+
assert_equal [[1, 0], [2, 1], [3, 2]], pairs
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'with_index' do
|
57
|
+
nums = []
|
58
|
+
@enum.with_index do |x, i|
|
59
|
+
nums << (x - i)
|
60
|
+
end
|
61
|
+
assert_equal [1, 1, 1, 1, 1], nums
|
62
|
+
end
|
63
|
+
|
64
|
+
test 'with_object no block' do
|
65
|
+
h = {}
|
66
|
+
pair = @enum.with_object(h).to_a.first
|
67
|
+
assert_equal [ 1, h ], pair
|
68
|
+
end
|
69
|
+
|
70
|
+
test 'with_object' do
|
71
|
+
to_i = @enum.with_object({}) do |x, hash|
|
72
|
+
hash[ x.to_s ] = x
|
73
|
+
end
|
74
|
+
assert_equal to_i['3'], 3
|
75
|
+
end
|
76
|
+
|
77
|
+
test 'map no block' do
|
78
|
+
assert_raises(ArgumentError) { @enum.map }
|
79
|
+
end
|
80
|
+
|
81
|
+
test 'map' do
|
82
|
+
squares = @enum.map{ |i| i * i }
|
83
|
+
assert_equal [1, 4, 9, 16, 25], squares
|
84
|
+
end
|
85
|
+
|
86
|
+
test 'map keeps order' do
|
87
|
+
strs = @enum.map{ |i| sleep rand; i.to_s }
|
88
|
+
assert_equal '1 2 3 4 5', strs.join(' ')
|
89
|
+
end
|
90
|
+
|
91
|
+
test 'lock in block' do
|
92
|
+
count = 0
|
93
|
+
1000.times.async(5).each do
|
94
|
+
lock :count do
|
95
|
+
count += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
assert_equal 1000, count
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ThreadPoolTest < Test
|
4
|
+
|
5
|
+
test 'pools can speed things up' do
|
6
|
+
default_start = Time.now
|
7
|
+
500.times.async.each{ true }
|
8
|
+
default_delta = Time.now - default_start
|
9
|
+
|
10
|
+
rated_start = Time.now
|
11
|
+
500.times.async(10).each{ true }
|
12
|
+
rated_delta = Time.now - rated_start
|
13
|
+
|
14
|
+
assert rated_delta < default_delta
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'pools with ranges' do
|
18
|
+
vals = (0..7).async(4).map do |x|
|
19
|
+
x + 2
|
20
|
+
end
|
21
|
+
assert_equal [2, 3, 4, 5, 6, 7, 8, 9], vals
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/benchmark'
|
3
|
+
require 'async_enum'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
Test = MiniTest::Unit::TestCase
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
def setup(&block)
|
12
|
+
define_method('setup', &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test(test_name, &block)
|
16
|
+
define_method("test_#{ test_name.gsub(/\s+/, '_') }", &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def cleanup(&block)
|
20
|
+
define_method('cleanup', &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def teardown(&block)
|
24
|
+
define_method('teardown', &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
def assert_singleton_method(object, sym)
|
30
|
+
assert object.respond_to?(sym), "Expected #{object} singleton to respond to #{sym}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_instance_method(object, sym)
|
34
|
+
assert object.method_defined?(sym), "Expected #{object} instance to respond to #{sym}"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: async_enum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- aj0strow
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: iterate over enumerable objects concurrently
|
42
|
+
email: alexander.ostrow@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- .travis.yml
|
49
|
+
- Gemfile
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- async_enum.gemspec
|
53
|
+
- demos/lock.rb
|
54
|
+
- demos/pool_size.rb
|
55
|
+
- demos/sleep.rb
|
56
|
+
- demos/sync.rb
|
57
|
+
- lib/async_enum.rb
|
58
|
+
- lib/enumerator/async.rb
|
59
|
+
- test/async_enum_test/benchmark_test.rb
|
60
|
+
- test/async_enum_test/definitions_test.rb
|
61
|
+
- test/async_enum_test/enumerator_async_test.rb
|
62
|
+
- test/async_enum_test/thread_pool_test.rb
|
63
|
+
- test/test_helper.rb
|
64
|
+
homepage: http://github.com/aj0strow/async_enum
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.0.5
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Async Enumerable and Enumerator
|
88
|
+
test_files:
|
89
|
+
- test/async_enum_test/benchmark_test.rb
|
90
|
+
- test/async_enum_test/definitions_test.rb
|
91
|
+
- test/async_enum_test/enumerator_async_test.rb
|
92
|
+
- test/async_enum_test/thread_pool_test.rb
|
93
|
+
- test/test_helper.rb
|