go_chanel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67dbc282f9ad1d4a8d5e0c41bd263f206e576c05
4
+ data.tar.gz: 94642ea6a66a3aba7296356cba9f0c327b4b4880
5
+ SHA512:
6
+ metadata.gz: b948c6a7297ebc6a87323835759267819420b7f97781cc93463d256413a4d6dcc4f53b277f1fb6f33978d97e503066a221597c89d784ac01b9468e7f829315ad
7
+ data.tar.gz: bd647e0dcdce5a93d7609e66052566e46b301bc2890ee97b6f5e817cfa7e22939cd26e7307f92928b478b47210a1680b6537297082e73e9513a51fae3541f820
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'http://ruby.taobao.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry', '~> 0.10.1'
7
+ gem 'mocha', :require => false
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 xuxiangyang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # GoChanel
2
+ 模仿golang中的channel。chanel是因为我拼写错了。。。。
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ gem 'go_chanel'
9
+
10
+ And then execute:
11
+
12
+ $ bundle
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install go_chanel
17
+
18
+ ## Usage
19
+
20
+ 最重要的组成是channel。也就是`GOChanel::Chanel`。它是所有的核心。
21
+ 一个channel是这样的:
22
+ * 它是一个FIFO的队列,它支持push、pop、close。默认容量是1
23
+ * 向一个Buffer满了的channel中push会阻塞
24
+ * 向一个closed的channel中push会抛出异常
25
+ * 从一个Buffer空了的channel中pop会阻塞
26
+ * 从一个closed的channle中pop不会抛异常,会返回一个变量说channel关闭了
27
+ 总之他就是在模拟golang中的channel
28
+
29
+ ###GoChanel::Chanel的用法例子
30
+ ```ruby
31
+ chanel = GoChanel::Chanel.new # 新建了一个容量为1的chanel
32
+ chanel.push(1) #向chanel中push一次,这时还不会阻塞
33
+ obj, ok = chanel.pop #obj是队列里的东西,ok是说这个chanel有没有关闭,如果close的话ok就会是false
34
+ chanel.close #这个一般是在完成push之后关闭
35
+ ```
36
+
37
+ 更复杂点的用法,一般是主线程只负责分线程。我喜欢这样的用法:
38
+ ```ruby
39
+ def main
40
+ concurrency_chan = GoChanel::Chanel.new
41
+ Thread.new {
42
+ puts "hello world"
43
+ concurrency_chan.push(true)
44
+ }
45
+ concurrency_chan.pop
46
+ end
47
+ ```
48
+
49
+ 更复杂的用法, 也就是更高的并发
50
+
51
+ ```ruby
52
+ def main
53
+ finish_func1 = GoChanel::Chanel.new
54
+ finish_func2 = GoChanel::Chanel.new
55
+ buffer_chan = GoChanel::Chanel.new(128)
56
+ Thread.new {
57
+ func1(buffer_chan)
58
+ finish_func1.push(true)
59
+ }
60
+ Thread.new {
61
+ func2(buffer_chan)
62
+ finish_func2.push(true)
63
+ }
64
+ finish_func1.pop
65
+ finish_func2.pop
66
+ end
67
+ def func1(out_chan)
68
+ 1024.times do |i|
69
+ out_chan.push(i)
70
+ end
71
+ out_chan.close
72
+ end
73
+ def func2(in_chan)
74
+ concurrency_chan = GoChanel::Chanel.new(512) #512个并发
75
+ while true do
76
+ obj, ok = in_chan.pop
77
+ break unless ok
78
+ concurrency_chan.push(true)
79
+ Thread.new(obj) do |str|
80
+ puts str
81
+ concurrency_chan.pop
82
+ end
83
+ end
84
+ end
85
+ ```
86
+
87
+ ###并发的Pipe
88
+ 类似于linux中的pipe,一个pipe的定义是这样的
89
+ * 由至少2个pool组成
90
+ * 分为三类pool,头、中间、尾。头只有一个out_chan, 中间的有一个in_chan也有一个out_chan, 尾只有一个in_chan
91
+ * 对于中间和尾部的pool, 前一个pool的block返回值应该可以作为下一个pool block的输入
92
+ * 头pool比较特殊,其实他是单线程执行的,因为一般头pool是重cpu的操作,分成多个thread也没有意义,它的返回值是一个array,依次传递给下一个pool
93
+ 用法如下,还是完成上一个复杂的例子,就会很简单
94
+
95
+ ```ruby
96
+ pool1 = GoChanel::Pool.new(1) do
97
+ 1024.times.to_a
98
+ end
99
+ pool2 = GoChanle::Pool.new(512) do |i|
100
+ puts i
101
+ end
102
+ pipe = pool1 | pool2
103
+ pipe.run
104
+ ```
105
+
106
+ 另一个例子
107
+
108
+ ```ruby
109
+ pool1 = GoChanel::Pool.new do
110
+ 1024.times.map do |i|
111
+ [i + 1, i + 2]
112
+ end
113
+ end
114
+ pool2 = GoChanel::Pool.new(128) do |i, j|
115
+ i + j #一般为IO操作,这里就最简单的表示了
116
+ end
117
+ pool3 = GoChanel::Pool.new(32) do |i|
118
+ puts i
119
+ end
120
+ pipe = pool1 | pool2 | pool3
121
+ pipe.run
122
+ ```
123
+
124
+ ###很重要:记住Thread和Progress的区别
125
+ ## Contributing
126
+
127
+ 1. Fork it ( https://github.com/xuxiangyang/go_chanel/fork )
128
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
129
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
130
+ 4. Push to the branch (`git push origin my-new-feature`)
131
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
data/go_chanel.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'go_chanel'
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "go_chanel"
7
+ spec.version = GoChanel::VERSION
8
+ spec.authors = ["xuxiangyang"]
9
+ spec.email = ["54049924@qq.com"]
10
+ spec.summary = %q{simulate golang channel}
11
+ spec.description = %q{simulate golang channel}
12
+ spec.homepage = "https://github.com/xuxiangyang/go_chanel"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib/"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake", "~> 10.4"
22
+ end
data/lib/go_chanel.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "go_chanel/version"
2
+ require "go_chanel/chanel"
3
+ require "go_chanel/pool"
4
+ require "go_chanel/pipe"
5
+ require "go_chanel/task"
6
+ require "go_chanel/err_notifier"
7
+
8
+ module GoChanel
9
+ def self.go(*args, &proc)
10
+ Thread.new(args) do |params|
11
+ proc.call(*params)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ # chanel is a FIFO queue.
2
+ # It will sleep when push into a channel with full buffer.
3
+ # It will sleep when pop from an empty channel
4
+ # It will raise Exception when push into a closed channel
5
+ # It will still work when pop form an empty channel
6
+ # just like go channel
7
+
8
+ module GoChanel
9
+ class Chanel
10
+ def initialize(cap_size = 1, objs = [])
11
+ raise ChanelException.new("can not create a 0 size channel") if cap_size < 1
12
+
13
+ @obj_mutex = Mutex.new
14
+ @close_mutex = Mutex.new
15
+ @cap_size = cap_size
16
+ @objs = objs
17
+ @closed = false
18
+ end
19
+
20
+ def push(obj)
21
+ raise ChanelException.new("can not push to closed channel") if @closed
22
+
23
+ while @objs.count >= @cap_size do
24
+ raise ChanelException.new("can not push to closed channel") if @closed
25
+ sleep(0.1)
26
+ end
27
+
28
+ @obj_mutex.synchronize do
29
+ @objs.push(obj)
30
+ end
31
+ end
32
+
33
+ def pop
34
+ while @objs.count == 0 do
35
+ return nil, false if @closed
36
+ sleep(0.1)
37
+ end
38
+
39
+ obj = @obj_mutex.synchronize do
40
+ @objs.shift
41
+ end
42
+
43
+ return obj, true
44
+ end
45
+
46
+ def size
47
+ @objs.count
48
+ end
49
+
50
+ def cap
51
+ @cap_size
52
+ end
53
+
54
+ def close
55
+ @close_mutex.synchronize do
56
+ @closed = true
57
+ end
58
+ end
59
+ end
60
+
61
+ class ChanelException < StandardError
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ module GoChanel
2
+ class ErrNotifier
3
+ @@notifier = nil
4
+ @@std_out = true
5
+ @@mutex = Mutex.new
6
+ def self.register(notifier, std_out = false)
7
+ @@notifier = notifier
8
+ @@std_out = std_out
9
+ raise "notifier should have a func named as" unless notifier.respond_to?(:notify)
10
+ end
11
+
12
+ def self.std_out=(open = true)
13
+ @@std_out = open
14
+ end
15
+
16
+ def self.notify(e)
17
+ if @@std_out
18
+ puts "Something wrong in Thread:"
19
+ puts e
20
+ puts e.backtrace
21
+ end
22
+
23
+ if @@notifier.present?
24
+ @@mutex.synchronize do
25
+ notifier.notify(e)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,82 @@
1
+ module GoChanel
2
+ class Pipe
3
+ attr_accessor :pools
4
+ def self.|(pool)
5
+ pipe = Pipe.new
6
+ pipe.pools << pool
7
+ pipe
8
+ end
9
+
10
+ def initialize
11
+ @pools = []
12
+ end
13
+
14
+ def |(pool)
15
+ @pools << pool
16
+ self
17
+ end
18
+
19
+ def run(*args)
20
+ raise PipeException.new("at_leaset pipe two pools") if @pools.count < 2
21
+ concurrence_chan = GoChanel::Chanel.new(@pools.count)
22
+
23
+ chans = build_chans
24
+
25
+ start_pool = @pools.shift
26
+ last_pool = @pools.pop
27
+
28
+ Thread.new(args) do |params|
29
+ begin
30
+ GoChanel::Task.start_task(chans[0], *params, &start_pool.proc)
31
+ rescue => e
32
+ GoChanel::ErrNotifier.notify(e)
33
+ ensure
34
+ concurrence_chan.push(true)
35
+ end
36
+ end
37
+
38
+ #no head and tail
39
+ @pools.each_with_index do |pool, i|
40
+ Thread.new do
41
+ begin
42
+ GoChanel::Task.normal_task(chans[i], chans[i + 1], pool.size, &pool.proc)
43
+ rescue => e
44
+ GoChanel::ErrNotifier.notify(e)
45
+ ensure
46
+ concurrence_chan.push(true)
47
+ end
48
+ end
49
+ end
50
+
51
+ Thread.new do
52
+ begin
53
+ GoChanel::Task.end_task(chans[-1], last_pool.size, &last_pool.proc)
54
+ rescue => e
55
+ GoChanel::ErrNotifier.notify(e)
56
+ ensure
57
+ concurrence_chan.push(true)
58
+ end
59
+ end
60
+
61
+ concurrence_chan.cap.times do
62
+ concurrence_chan.pop
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def build_chans
69
+ chans = []
70
+
71
+ @pools.each_with_index do |pool, i|
72
+ next if i == 0
73
+ chans << GoChanel::Chanel.new(pool.size)
74
+ end
75
+
76
+ chans
77
+ end
78
+ end
79
+
80
+ class PipeException < StandardError
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ require "go_chanel/chanel"
2
+ module GoChanel
3
+ class Pool
4
+ attr_reader :size, :proc
5
+ def initialize(size = 1, &proc)
6
+ @size = size
7
+ @proc = proc
8
+ end
9
+
10
+ def |(pool)
11
+ pipe = Pipe.new
12
+ pipe.pools << self
13
+ pipe.pools << pool
14
+ pipe
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GoChanel
3
+ class Task
4
+ def self.normal_task(in_chan, out_chan, concurrence_count = 1, &proc)
5
+ concurrence_chan = GoChanel::Chanel.new(concurrence_count)
6
+ while true do
7
+ args, ok = in_chan.pop
8
+ break unless ok
9
+ concurrence_chan.push(true)
10
+ Thread.new(args) do |params|
11
+ begin
12
+ result = proc.call(*params)
13
+ out_chan.push(result)
14
+ rescue => e
15
+ GoChanel::ErrNotifier.notify(e)
16
+ ensure
17
+ concurrence_chan.pop
18
+ end
19
+ end
20
+ end
21
+
22
+ while concurrence_chan.size > 0 do
23
+ sleep(0.1)
24
+ end
25
+ out_chan.close
26
+ end
27
+
28
+ def self.start_task(out_chan, *args, &proc)
29
+ concurrence_chan = GoChanel::Chanel.new
30
+ Thread.new do
31
+ begin
32
+ proc.call(*args).each do |result|
33
+ out_chan.push(result)
34
+ end
35
+ rescue => e
36
+ GoChanel::ErrNotifier.notify(e)
37
+ ensure
38
+ concurrence_chan.push(true)
39
+ end
40
+ end
41
+ concurrence_chan.pop
42
+ out_chan.close
43
+ end
44
+
45
+ def self.end_task(in_chan, concurrence_count, &proc)
46
+ concurrence_chan = GoChanel::Chanel.new(concurrence_count)
47
+ while true do
48
+ args, ok = in_chan.pop
49
+ break unless ok
50
+ concurrence_chan.push(true)
51
+ Thread.new(args) do |params|
52
+ begin
53
+ proc.call(*params)
54
+ rescue => e
55
+ GoChanel::ErrNotifier.notify(e)
56
+ ensure
57
+ concurrence_chan.pop
58
+ end
59
+ end
60
+ end
61
+
62
+ while concurrence_chan.size > 0 do
63
+ sleep(0.1)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module GoChanel
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,71 @@
1
+ require 'test/unit'
2
+ require 'mocha/test_unit'
3
+
4
+ class TestChanel < Test::Unit::TestCase
5
+ def test_0_cap_new_will_raise
6
+ assert_raise GoChanel::ChanelException do
7
+ GoChanel::Chanel.new(0)
8
+ end
9
+ end
10
+
11
+ def test_push_to_closed_channel_will_raise
12
+ chanel = GoChanel::Chanel.new
13
+ chanel.close
14
+
15
+ assert_raise GoChanel::ChanelException do
16
+ chanel.push(2)
17
+ end
18
+ end
19
+
20
+ def test_pop_from_closed_channel_will_not_raise
21
+ chanel = GoChanel::Chanel.new
22
+ chanel.push(1)
23
+ chanel.close
24
+
25
+ assert_nothing_raised do
26
+ chanel.pop
27
+ end
28
+ end
29
+
30
+ def test_pop_from_closed_will_return_not_ok
31
+ chanel = GoChanel::Chanel.new
32
+ chanel.push(1)
33
+ chanel.close
34
+
35
+ assert_equal [1, true], chanel.pop
36
+ assert_equal [nil, false], chanel.pop
37
+ end
38
+
39
+ def test_channel_is_fifo
40
+ chanel = GoChanel::Chanel.new(3)
41
+ chanel.push(1)
42
+ chanel.push(2)
43
+ chanel.push(3)
44
+
45
+ assert_equal 1, chanel.pop.first
46
+ assert_equal 2, chanel.pop.first
47
+ assert_equal 3, chanel.pop.first
48
+ end
49
+
50
+ def test_empty_chanel_pop_will_sleep
51
+ chanel = GoChanel::Chanel.new
52
+ chanel.push(1)
53
+ chanel.pop
54
+
55
+ chanel.stubs(:sleep).raises(Exception)
56
+
57
+ assert_raise Exception do
58
+ chanel.pop
59
+ end
60
+ end
61
+
62
+ def test_full_chanel_push_will_sleep
63
+ chanel = GoChanel::Chanel.new
64
+ chanel.push(1)
65
+ chanel.stubs(:sleep).raises(Exception)
66
+
67
+ assert_raise Exception do
68
+ chanel.push(1)
69
+ end
70
+ end
71
+ end
data/test/test_go.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'test/unit'
2
+
3
+ class TestGo < Test::Unit::TestCase
4
+ def test_go
5
+ finish_push_chanel = GoChanel::Chanel.new
6
+ finish_pop_chanel = GoChanel::Chanel.new
7
+ chanel = GoChanel::Chanel.new
8
+ GoChanel.go(1) do |a|
9
+ chanel.push(1)
10
+ finish_push_chanel.push(true)
11
+ end
12
+
13
+
14
+ GoChanel.go do
15
+ finish_pop_chanel.push(true)
16
+ end
17
+ finish_push_chanel.pop
18
+ finish_pop_chanel.pop
19
+ end
20
+ end
data/test/test_pipe.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'test/unit'
2
+ require 'pry'
3
+
4
+ class TestPipe < Test::Unit::TestCase
5
+ def test_pipe
6
+ count = 0
7
+ pool1 = GoChanel::Pool.new do |i|
8
+ [1]
9
+ end
10
+
11
+ pool2 = GoChanel::Pool.new do |i|
12
+ [i + 1, i + 2]
13
+ end
14
+
15
+ pool3 = GoChanel::Pool.new do |i, j|
16
+ count = i + j
17
+ end
18
+
19
+ pipe = pool1 | pool2 | pool3
20
+ pipe.run
21
+
22
+ assert_equal 5, count
23
+ end
24
+
25
+ def test_pipe_at_least_2_pool
26
+ pool1 = GoChanel::Pool.new(1) do |i|
27
+ i
28
+ end
29
+
30
+ pipe = GoChanel::Pipe | pool1
31
+ assert_raise GoChanel::PipeException do
32
+ pipe.run
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: go_chanel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - xuxiangyang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-30 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: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.4'
41
+ description: simulate golang channel
42
+ email:
43
+ - 54049924@qq.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - go_chanel.gemspec
54
+ - lib/go_chanel.rb
55
+ - lib/go_chanel/chanel.rb
56
+ - lib/go_chanel/err_notifier.rb
57
+ - lib/go_chanel/pipe.rb
58
+ - lib/go_chanel/pool.rb
59
+ - lib/go_chanel/task.rb
60
+ - lib/go_chanel/version.rb
61
+ - test/test_chanel.rb
62
+ - test/test_go.rb
63
+ - test/test_pipe.rb
64
+ homepage: https://github.com/xuxiangyang/go_chanel
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.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: simulate golang channel
88
+ test_files:
89
+ - test/test_chanel.rb
90
+ - test/test_go.rb
91
+ - test/test_pipe.rb