rbgo 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +21 -0
- data/README.md +113 -0
- data/lib/rbgo/corun.rb +192 -0
- data/lib/rbgo/network_service.rb +128 -0
- data/lib/rbgo/select_chan.rb +383 -0
- data/lib/rbgo/version.rb +3 -0
- data/lib/rbgo/wait_group.rb +46 -0
- data/lib/rbgo.rb +4 -0
- data/rbgo.gemspec +25 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d9e43797ac2188d0ed30432699def2789333d41d5dbacfacbc34710af4f1c505
|
4
|
+
data.tar.gz: 36b4745d925d33b30c759295d36337de1b283668959f922149d02e5fbc8c0738
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ca341dd5a7f430802ca235d01906ef67b801238c5566e1f415838c742ad28e10ac6d28862fa8bfde87f688bf2ada43a5bfd3fc21603f0933df8b8c620c3080e0
|
7
|
+
data.tar.gz: 023e7bbcbb60fff0b8768b773a0176753af86bf72fa970d48410a17ffe8dcd7f6d8bcf7b43d7ca77760bbf581a097cb1ae7481dd31a00dc7078c4b66a2725f3f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 王寅
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# I like the way how golang does in concurrency.
|
2
|
+
|
3
|
+
You can produce a light weight routine easily with a keyword 'go' and communicate with each routine by channel.
|
4
|
+
|
5
|
+
In MRI the GIL prevents you from running code parallelly, but there ara also other ruby implementations such as TruffleRuby or JRuby which can utilize all CPU cores.
|
6
|
+
|
7
|
+
In MRI write program to run concurrently even not parallelly is also important.
|
8
|
+
|
9
|
+
# This project is trying to help writing concurrent program with ruby a little easier.
|
10
|
+
|
11
|
+
# select_chan
|
12
|
+
select channels like golang in ruby
|
13
|
+
|
14
|
+
Chan is buffered or non-buffered, just like make(chan Type,n) or make(chan Type) in golang
|
15
|
+
but not statically typed in my implementation.
|
16
|
+
|
17
|
+
example:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require_relative 'select_chan'
|
21
|
+
|
22
|
+
include Channel
|
23
|
+
|
24
|
+
ch1 = Chan.new # non-buffer channel
|
25
|
+
ch2 = Chan.new(2) # buffer channel
|
26
|
+
ch3 = Chan.new(1)
|
27
|
+
|
28
|
+
ch3 << 'hello'
|
29
|
+
|
30
|
+
select_chan(
|
31
|
+
on_read(chan: ch1){|obj, ok| # obj is the value read from ch1, ok indicates success or failure by close
|
32
|
+
#do when read success
|
33
|
+
},
|
34
|
+
on_read(chan: ch2){
|
35
|
+
#do when read success
|
36
|
+
},
|
37
|
+
on_write(chan: ch3, obj: 'world'){
|
38
|
+
#do when write success
|
39
|
+
}
|
40
|
+
){ puts 'call default block' }
|
41
|
+
```
|
42
|
+
# go
|
43
|
+
create lightweight routine like golang
|
44
|
+
|
45
|
+
example:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
require_relative 'corun'
|
49
|
+
require_relative 'wait_group'
|
50
|
+
|
51
|
+
using CoRunExtensions
|
52
|
+
|
53
|
+
wg = WaitGroup.new
|
54
|
+
|
55
|
+
wg.add(1)
|
56
|
+
go do
|
57
|
+
puts 'start'
|
58
|
+
|
59
|
+
Fiber.yield # like Gosched()
|
60
|
+
|
61
|
+
puts 'end'
|
62
|
+
|
63
|
+
wg.done
|
64
|
+
end
|
65
|
+
|
66
|
+
wg.add(1)
|
67
|
+
go do
|
68
|
+
sleep 1
|
69
|
+
puts 'sleep end'
|
70
|
+
wg.done
|
71
|
+
end
|
72
|
+
|
73
|
+
wg.wait
|
74
|
+
puts 'wg.wait done'
|
75
|
+
|
76
|
+
```
|
77
|
+
# NetworkService
|
78
|
+
|
79
|
+
open TCP or UDP service
|
80
|
+
|
81
|
+
because service handles network request in async mode, it can handle many requests concurrently. If use some Non-GIL ruby implementations such as TruffleRuby or JRuby, it can utilize all your CPU cores.
|
82
|
+
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
require_relative 'network_service'
|
86
|
+
|
87
|
+
#localhost, port 3000
|
88
|
+
tcp_service = NetworkServiceFactory.open_tcp_service(3000) do|sock, clientAddrInfo|
|
89
|
+
p [sock, clientAddrInfo]
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
p "start tcp service: #{[tcp_service.host, tcp_service.port, tcp_service.type]}"
|
95
|
+
|
96
|
+
tcp_service.stop
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
#localhost, port auto pick
|
101
|
+
udp_service = NetworkServiceFactory.open_udp_service(0) do|msg, reply_msg|
|
102
|
+
p msg
|
103
|
+
reply_msg.reply("I receive your message")
|
104
|
+
end
|
105
|
+
|
106
|
+
p "start udp service: #{[udp_service.host, udp_service.port, udp_service.type]}"
|
107
|
+
|
108
|
+
udp_service.stop
|
109
|
+
|
110
|
+
|
111
|
+
sleep
|
112
|
+
|
113
|
+
```
|
data/lib/rbgo/corun.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'fiber'
|
3
|
+
require 'sys-cpu'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module Rbgo
|
7
|
+
module CoRun
|
8
|
+
|
9
|
+
class Routine
|
10
|
+
attr_accessor :error
|
11
|
+
|
12
|
+
def alive?
|
13
|
+
return fiber.alive? unless fiber.nil?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_accessor :args, :blk, :fiber
|
20
|
+
|
21
|
+
def initialize(*args, &blk)
|
22
|
+
self.args = args
|
23
|
+
self.blk = blk
|
24
|
+
Scheduler.instance.schedule(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform
|
28
|
+
self.fiber = Fiber.new do |args|
|
29
|
+
blk.call(*args)
|
30
|
+
end if fiber.nil?
|
31
|
+
|
32
|
+
if fiber.alive?
|
33
|
+
fiber.resume(*args)
|
34
|
+
end
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Scheduler
|
40
|
+
include Singleton
|
41
|
+
attr_accessor :num_thread
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_accessor :thread_pool
|
46
|
+
attr_accessor :task_queue
|
47
|
+
attr_accessor :msg_queue
|
48
|
+
attr_accessor :supervisor_thread
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
self.num_thread = Sys::CPU.num_cpu
|
52
|
+
self.thread_pool = []
|
53
|
+
|
54
|
+
self.msg_queue = Queue.new
|
55
|
+
self.task_queue = Queue.new
|
56
|
+
|
57
|
+
msg_queue << :init
|
58
|
+
create_supervisor_thread
|
59
|
+
generate_check_msg
|
60
|
+
end
|
61
|
+
|
62
|
+
# only called by supervisor thread
|
63
|
+
def create_thread
|
64
|
+
begin
|
65
|
+
thread_pool << Thread.new do
|
66
|
+
Thread.current.report_on_exception = false
|
67
|
+
begin
|
68
|
+
should_exit = false
|
69
|
+
yield_task_queue = Queue.new
|
70
|
+
local_task_queue = Queue.new
|
71
|
+
loop do
|
72
|
+
task = nil
|
73
|
+
if local_task_queue.empty?
|
74
|
+
task = task_queue.deq(true) rescue nil
|
75
|
+
if task.nil?
|
76
|
+
task = yield_task_queue.deq unless yield_task_queue.empty?
|
77
|
+
task = task_queue.deq if task.nil?
|
78
|
+
local_task_queue << task
|
79
|
+
else
|
80
|
+
local_task_queue << task
|
81
|
+
local_task_queue << yield_task_queue.deq unless yield_task_queue.empty?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
task = local_task_queue.deq
|
85
|
+
|
86
|
+
begin
|
87
|
+
Thread.current.thread_variable_set(:performing, true)
|
88
|
+
task.send :perform
|
89
|
+
rescue Exception => ex
|
90
|
+
task.error = ex
|
91
|
+
next
|
92
|
+
ensure
|
93
|
+
Thread.current.thread_variable_set(:performing, false)
|
94
|
+
end
|
95
|
+
|
96
|
+
if task.alive?
|
97
|
+
yield_task_queue << task
|
98
|
+
end
|
99
|
+
|
100
|
+
should_exit = Thread.current.thread_variable_get(:should_exit) &&
|
101
|
+
yield_task_queue.empty? &&
|
102
|
+
local_task_queue.empty?
|
103
|
+
break if should_exit
|
104
|
+
end
|
105
|
+
ensure
|
106
|
+
msg_queue << :thread_exit unless should_exit
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rescue Exception => ex
|
110
|
+
STDERR.puts ex
|
111
|
+
end
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def create_supervisor_thread
|
116
|
+
self.supervisor_thread = Thread.new do
|
117
|
+
begin
|
118
|
+
loop do
|
119
|
+
msg = msg_queue.deq
|
120
|
+
case msg
|
121
|
+
when :thread_exit, :init, :check
|
122
|
+
check_thread_pool
|
123
|
+
end
|
124
|
+
end
|
125
|
+
ensure
|
126
|
+
STDERR.puts 'supervisor thread exit'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def generate_check_msg
|
133
|
+
Thread.new do
|
134
|
+
begin
|
135
|
+
loop do
|
136
|
+
msg_queue << :check
|
137
|
+
sleep 3
|
138
|
+
end
|
139
|
+
ensure
|
140
|
+
STDERR.puts 'check generator thread exit'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# only called by supervisor thread
|
147
|
+
def check_thread_pool
|
148
|
+
temp = []
|
149
|
+
thread_pool.each do |th|
|
150
|
+
case th.status
|
151
|
+
when 'run'
|
152
|
+
temp << th
|
153
|
+
when 'sleep'
|
154
|
+
performing = th.thread_variable_get(:performing)
|
155
|
+
if performing
|
156
|
+
th.thread_variable_set(:should_exit, true)
|
157
|
+
else
|
158
|
+
temp << th
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
self.thread_pool = temp
|
163
|
+
n = num_thread - thread_pool.size
|
164
|
+
if n > 0
|
165
|
+
n.times { create_thread }
|
166
|
+
elsif n < 0
|
167
|
+
n = -n
|
168
|
+
thread_pool.take(n).each do |th|
|
169
|
+
th.thread_variable_set(:should_exit, true)
|
170
|
+
end
|
171
|
+
self.thread_pool = thread_pool.drop(n)
|
172
|
+
end
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
176
|
+
public
|
177
|
+
|
178
|
+
def schedule(routine)
|
179
|
+
task_queue << routine
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
module CoRunExtensions
|
186
|
+
refine Object do
|
187
|
+
def go(*args, &blk)
|
188
|
+
CoRun::Routine.new(*args, &blk)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'select_chan'
|
3
|
+
require_relative 'corun'
|
4
|
+
|
5
|
+
module Rbgo
|
6
|
+
module NetworkServiceFactory
|
7
|
+
using CoRunExtensions
|
8
|
+
include Channel
|
9
|
+
|
10
|
+
class Service
|
11
|
+
attr_reader :host, :port, :type
|
12
|
+
attr_accessor :task
|
13
|
+
|
14
|
+
def alive?
|
15
|
+
service_routine.alive?
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
sockets.each do |sock|
|
20
|
+
sock.close
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_accessor :service_routine, :sockets
|
27
|
+
attr_writer :host, :port, :type
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
self.type = :unknown
|
31
|
+
self.host = nil
|
32
|
+
self.port = 0
|
33
|
+
self.sockets = []
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
private :new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def open_tcp_service(host = nil, port, &blk)
|
42
|
+
|
43
|
+
res_chan = Chan.new(1)
|
44
|
+
service = Service.send :new
|
45
|
+
|
46
|
+
routine = go do
|
47
|
+
service.send :type=, :tcp
|
48
|
+
service.send :host=, host
|
49
|
+
service.send :port=, port
|
50
|
+
service.task = blk
|
51
|
+
begin
|
52
|
+
Socket.tcp_server_sockets(host, port) do |sockets|
|
53
|
+
service.send :port=, sockets.first.local_address.ip_port
|
54
|
+
service.send :sockets=, sockets
|
55
|
+
|
56
|
+
res_chan << service
|
57
|
+
|
58
|
+
begin
|
59
|
+
Socket.accept_loop(sockets) do |sock, clientAddrInfo|
|
60
|
+
go do
|
61
|
+
begin
|
62
|
+
service.task.call(sock, clientAddrInfo) unless service.task.nil?
|
63
|
+
ensure
|
64
|
+
sock.close
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
rescue Exception => ex
|
69
|
+
STDERR.puts ex
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
rescue Exception => ex
|
74
|
+
res_chan << service
|
75
|
+
STDERR.puts ex
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
service.send :service_routine=, routine
|
80
|
+
|
81
|
+
res_chan.deq
|
82
|
+
service
|
83
|
+
end
|
84
|
+
|
85
|
+
def open_udp_service(host = nil, port, &blk)
|
86
|
+
|
87
|
+
res_chan = Chan.new(1)
|
88
|
+
service = Service.send :new
|
89
|
+
|
90
|
+
routine = go do
|
91
|
+
service.send :type=, :udp
|
92
|
+
service.send :host=, host
|
93
|
+
service.send :port=, port
|
94
|
+
service.task = blk
|
95
|
+
begin
|
96
|
+
Socket.udp_server_sockets(host, port) do |sockets|
|
97
|
+
service.send :port=, sockets.first.local_address.ip_port
|
98
|
+
service.send :sockets=, sockets
|
99
|
+
|
100
|
+
res_chan << service
|
101
|
+
|
102
|
+
begin
|
103
|
+
Socket.udp_server_loop_on(sockets) do |msg, msg_src|
|
104
|
+
go do
|
105
|
+
service.task.call(msg, msg_src) unless service.task.nil?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
rescue Exception => ex
|
109
|
+
STDERR.puts ex
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
rescue Exception => ex
|
114
|
+
res_chan << service
|
115
|
+
STDERR.puts ex
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
service.send :service_routine, routine
|
120
|
+
|
121
|
+
res_chan.deq
|
122
|
+
service
|
123
|
+
end
|
124
|
+
|
125
|
+
module_function :open_tcp_service, :open_udp_service
|
126
|
+
public :open_tcp_service, :open_udp_service
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,383 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'set'
|
3
|
+
require 'monitor'
|
4
|
+
|
5
|
+
module Rbgo
|
6
|
+
module Channel
|
7
|
+
module Chan
|
8
|
+
|
9
|
+
def self.new(max = 0)
|
10
|
+
if max <= 0
|
11
|
+
NonBufferChan.new
|
12
|
+
else
|
13
|
+
BufferChan.new(max)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def register(observer:, mode: :rw)
|
20
|
+
unless observer.is_a? ConditionVariable
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
mode = mode.to_sym.downcase
|
24
|
+
if mode == :rw
|
25
|
+
@readable_observers.synchronize do
|
26
|
+
@readable_observers.add(observer)
|
27
|
+
end
|
28
|
+
@writable_observers.synchronize do
|
29
|
+
@writable_observers.add(observer)
|
30
|
+
end
|
31
|
+
elsif mode == :r
|
32
|
+
@readable_observers.synchronize do
|
33
|
+
@readable_observers.add(observer)
|
34
|
+
end
|
35
|
+
elsif mode == :w
|
36
|
+
@writable_observers.synchronize do
|
37
|
+
@writable_observers.add(observer)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def unregister(observer:, mode: :rw)
|
46
|
+
mode = mode.to_sym.downcase
|
47
|
+
if mode == :rw
|
48
|
+
@readable_observers.synchronize do
|
49
|
+
@readable_observers.delete(observer)
|
50
|
+
end
|
51
|
+
@writable_observers.synchronize do
|
52
|
+
@writable_observers.delete(observer)
|
53
|
+
end
|
54
|
+
elsif mode == :r
|
55
|
+
@readable_observers.synchronize do
|
56
|
+
@readable_observers.delete(observer)
|
57
|
+
end
|
58
|
+
elsif mode == :w
|
59
|
+
@writable_observers.synchronize do
|
60
|
+
@writable_observers.delete(observer)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def notify_readable_observers
|
69
|
+
@readable_observers.each(&:broadcast)
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def notify_writable_observers
|
74
|
+
@writable_observers.each(&:broadcast)
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# NonBufferChan
|
80
|
+
#
|
81
|
+
#
|
82
|
+
#
|
83
|
+
#
|
84
|
+
#
|
85
|
+
class NonBufferChan
|
86
|
+
include Chan
|
87
|
+
|
88
|
+
def initialize
|
89
|
+
self.enq_mutex = Mutex.new
|
90
|
+
self.deq_mutex = Mutex.new
|
91
|
+
self.enq_cond = ConditionVariable.new
|
92
|
+
self.deq_cond = ConditionVariable.new
|
93
|
+
self.resource_array = []
|
94
|
+
self.close_flag = false
|
95
|
+
self.have_enq_waiting_flag = false
|
96
|
+
self.have_deq_waiting_flag = false
|
97
|
+
|
98
|
+
@readable_observers = Set.new
|
99
|
+
@readable_observers.extend(MonitorMixin)
|
100
|
+
@writable_observers = Set.new
|
101
|
+
@writable_observers.extend(MonitorMixin)
|
102
|
+
end
|
103
|
+
|
104
|
+
def push(obj, nonblock = false)
|
105
|
+
if closed?
|
106
|
+
raise ClosedQueueError.new
|
107
|
+
end
|
108
|
+
|
109
|
+
if nonblock
|
110
|
+
raise ThreadError.new unless enq_mutex.try_lock
|
111
|
+
else
|
112
|
+
enq_mutex.lock
|
113
|
+
end
|
114
|
+
|
115
|
+
begin
|
116
|
+
if nonblock
|
117
|
+
raise ThreadError.new unless have_deq_waiting_flag
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
if closed?
|
122
|
+
raise ClosedQueueError.new
|
123
|
+
else
|
124
|
+
deq_mutex.synchronize do
|
125
|
+
resource_array[0] = obj
|
126
|
+
enq_cond.signal
|
127
|
+
until resource_array.empty? || closed?
|
128
|
+
self.have_enq_waiting_flag = true
|
129
|
+
|
130
|
+
begin
|
131
|
+
Thread.new do
|
132
|
+
deq_mutex.synchronize do
|
133
|
+
# no op
|
134
|
+
end
|
135
|
+
notify_readable_observers
|
136
|
+
end
|
137
|
+
rescue Exception => ex
|
138
|
+
STDERR.puts ex
|
139
|
+
sleep 1
|
140
|
+
retry
|
141
|
+
end
|
142
|
+
|
143
|
+
deq_cond.wait(deq_mutex)
|
144
|
+
end
|
145
|
+
raise ClosedQueueError.new if closed?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
ensure
|
149
|
+
self.have_enq_waiting_flag = false
|
150
|
+
end
|
151
|
+
ensure
|
152
|
+
enq_mutex.unlock
|
153
|
+
end
|
154
|
+
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
def pop(nonblock = false)
|
159
|
+
resource = nil
|
160
|
+
if closed?
|
161
|
+
return [nil, false]
|
162
|
+
end
|
163
|
+
|
164
|
+
if nonblock
|
165
|
+
raise ThreadError.new unless deq_mutex.try_lock
|
166
|
+
else
|
167
|
+
deq_mutex.lock
|
168
|
+
end
|
169
|
+
|
170
|
+
begin
|
171
|
+
if nonblock
|
172
|
+
raise ThreadError.new unless have_enq_waiting_flag
|
173
|
+
end
|
174
|
+
|
175
|
+
while resource_array.empty? && !closed?
|
176
|
+
self.have_deq_waiting_flag = true
|
177
|
+
notify_writable_observers
|
178
|
+
enq_cond.wait(deq_mutex)
|
179
|
+
end
|
180
|
+
resource = resource_array.first
|
181
|
+
resource_array.clear
|
182
|
+
self.have_deq_waiting_flag = false
|
183
|
+
deq_cond.signal
|
184
|
+
ensure
|
185
|
+
deq_mutex.unlock
|
186
|
+
end
|
187
|
+
|
188
|
+
[resource, !closed?]
|
189
|
+
end
|
190
|
+
|
191
|
+
def close
|
192
|
+
self.close_flag = true
|
193
|
+
enq_cond.broadcast
|
194
|
+
deq_cond.broadcast
|
195
|
+
notify_readable_observers
|
196
|
+
notify_writable_observers
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
def closed?
|
201
|
+
close_flag
|
202
|
+
end
|
203
|
+
|
204
|
+
alias_method :<<, :push
|
205
|
+
alias_method :enq, :push
|
206
|
+
alias_method :deq, :pop
|
207
|
+
alias_method :shift, :pop
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
attr_accessor :enq_mutex, :deq_mutex, :enq_cond,
|
212
|
+
:deq_cond, :resource_array, :close_flag,
|
213
|
+
:have_enq_waiting_flag, :have_deq_waiting_flag
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# BufferChan
|
218
|
+
#
|
219
|
+
#
|
220
|
+
#
|
221
|
+
#
|
222
|
+
#
|
223
|
+
#
|
224
|
+
class BufferChan < SizedQueue
|
225
|
+
include Chan
|
226
|
+
include Enumerable
|
227
|
+
|
228
|
+
def each
|
229
|
+
if block_given?
|
230
|
+
loop do
|
231
|
+
begin
|
232
|
+
yield pop(true)
|
233
|
+
rescue ThreadError
|
234
|
+
return
|
235
|
+
end
|
236
|
+
end
|
237
|
+
else
|
238
|
+
enum_for(:each)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def initialize(max)
|
243
|
+
super(max)
|
244
|
+
@readable_observers = Set.new
|
245
|
+
@readable_observers.extend(MonitorMixin)
|
246
|
+
@writable_observers = Set.new
|
247
|
+
@writable_observers.extend(MonitorMixin)
|
248
|
+
end
|
249
|
+
|
250
|
+
def push(obj, nonblock = false)
|
251
|
+
super(obj, nonblock)
|
252
|
+
notify_readable_observers
|
253
|
+
self
|
254
|
+
rescue ThreadError
|
255
|
+
raise ClosedQueueError.new if closed?
|
256
|
+
raise
|
257
|
+
end
|
258
|
+
|
259
|
+
def pop(nonblock = false)
|
260
|
+
res = nil
|
261
|
+
begin
|
262
|
+
res = super(nonblock)
|
263
|
+
notify_writable_observers
|
264
|
+
res
|
265
|
+
rescue ThreadError
|
266
|
+
raise unless closed?
|
267
|
+
end
|
268
|
+
[res, !closed?]
|
269
|
+
end
|
270
|
+
|
271
|
+
def clear
|
272
|
+
super
|
273
|
+
notify_writable_observers
|
274
|
+
self
|
275
|
+
end
|
276
|
+
|
277
|
+
def close
|
278
|
+
super
|
279
|
+
notify_readable_observers
|
280
|
+
notify_writable_observers
|
281
|
+
self
|
282
|
+
end
|
283
|
+
|
284
|
+
alias_method :<<, :push
|
285
|
+
alias_method :enq, :push
|
286
|
+
alias_method :deq, :pop
|
287
|
+
alias_method :shift, :pop
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
# select_chan
|
292
|
+
#
|
293
|
+
#
|
294
|
+
#
|
295
|
+
#
|
296
|
+
#
|
297
|
+
#
|
298
|
+
#
|
299
|
+
def select_chan(*ops)
|
300
|
+
ops.shuffle!
|
301
|
+
|
302
|
+
mutex = Mutex.new
|
303
|
+
cond = ConditionVariable.new
|
304
|
+
|
305
|
+
loop do
|
306
|
+
|
307
|
+
ops.each do |op|
|
308
|
+
begin
|
309
|
+
return op.call
|
310
|
+
rescue ThreadError
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
return yield if block_given?
|
315
|
+
|
316
|
+
ops.each do |op|
|
317
|
+
op.register(cond)
|
318
|
+
end
|
319
|
+
|
320
|
+
mutex.synchronize do
|
321
|
+
cond.wait mutex
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
ensure
|
327
|
+
|
328
|
+
ops.each do |op|
|
329
|
+
op.unregister(cond)
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
# on_read
|
335
|
+
#
|
336
|
+
#
|
337
|
+
#
|
338
|
+
#
|
339
|
+
def on_read(chan:, &blk)
|
340
|
+
raise ArgumentError.new('chan must be a Chan') unless chan.is_a? Chan
|
341
|
+
op = Proc.new do
|
342
|
+
res, ok = chan.deq(true)
|
343
|
+
if blk.nil?
|
344
|
+
[res, ok]
|
345
|
+
else
|
346
|
+
blk.call(res, ok)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
op.define_singleton_method(:register) do |cond|
|
350
|
+
chan.send :register, observer: cond, mode: :r
|
351
|
+
end
|
352
|
+
op.define_singleton_method(:unregister) do |cond|
|
353
|
+
chan.send :unregister, observer: cond, mode: :r
|
354
|
+
end
|
355
|
+
op
|
356
|
+
end
|
357
|
+
|
358
|
+
# on_write
|
359
|
+
#
|
360
|
+
#
|
361
|
+
#
|
362
|
+
#
|
363
|
+
#
|
364
|
+
def on_write(chan:, obj:, &blk)
|
365
|
+
raise ArgumentError.new('chan must be a Chan') unless chan.is_a? Chan
|
366
|
+
op = Proc.new do
|
367
|
+
res = chan.enq(obj, true)
|
368
|
+
res = blk.call unless blk.nil?
|
369
|
+
res
|
370
|
+
end
|
371
|
+
|
372
|
+
op.define_singleton_method(:register) do |cond|
|
373
|
+
chan.send :register, observer: cond, mode: :w
|
374
|
+
end
|
375
|
+
op.define_singleton_method(:unregister) do |cond|
|
376
|
+
chan.send :unregister, observer: cond, mode: :w
|
377
|
+
end
|
378
|
+
op
|
379
|
+
end
|
380
|
+
|
381
|
+
module_function :select_chan, :on_read, :on_write
|
382
|
+
end
|
383
|
+
end
|
data/lib/rbgo/version.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Rbgo
|
4
|
+
class WaitGroup
|
5
|
+
def initialize(init_count = 0)
|
6
|
+
self.total_count = [0, init_count].max
|
7
|
+
self.mutex = Mutex.new
|
8
|
+
self.cond = ConditionVariable.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(count)
|
12
|
+
mutex.synchronize do
|
13
|
+
c = total_count + count
|
14
|
+
if c < 0
|
15
|
+
raise RuntimeError.new('WaitGroup counts < 0')
|
16
|
+
else
|
17
|
+
self.total_count = c
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def done
|
23
|
+
mutex.synchronize do
|
24
|
+
c = total_count - 1
|
25
|
+
if c < 0
|
26
|
+
raise RuntimeError.new('WaitGroup counts < 0')
|
27
|
+
else
|
28
|
+
self.total_count = c
|
29
|
+
end
|
30
|
+
cond.broadcast if c == 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait
|
35
|
+
mutex.synchronize do
|
36
|
+
while total_count > 0
|
37
|
+
cond.wait(mutex)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_accessor :total_count, :mutex, :cond
|
45
|
+
end
|
46
|
+
end
|
data/lib/rbgo.rb
ADDED
data/rbgo.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative "lib/rbgo/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rbgo"
|
7
|
+
s.version = Rbgo::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Wang Yin"]
|
10
|
+
s.email = ["24588062@qq.com"]
|
11
|
+
s.homepage = "https://github.com/wangyin-git/select_chan"
|
12
|
+
s.summary = "Write concurrent program with Ruby in Golang style"
|
13
|
+
s.description = <<-END
|
14
|
+
You can produce a light weight routine easily with a keyword 'go' and communicate with each routine by channel.
|
15
|
+
|
16
|
+
In MRI the GIL prevents you from running code parallelly, but there ara also other ruby implementations such as TruffleRuby or JRuby which can utilize all CPU cores.
|
17
|
+
|
18
|
+
In MRI write program to run concurrently even not parallelly is also important.
|
19
|
+
|
20
|
+
This project is trying to help writing concurrent program with ruby a little easier
|
21
|
+
END
|
22
|
+
s.add_dependency "sys-cpu", "~> 0.8"
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.license = 'MIT'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rbgo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wang Yin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sys-cpu
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
27
|
+
description: " You can produce a light weight routine easily with
|
28
|
+
a keyword 'go' and communicate with each routine by channel.\n\n In
|
29
|
+
MRI the GIL prevents you from running code parallelly, but there ara also other
|
30
|
+
ruby implementations such as TruffleRuby or JRuby which can utilize all CPU cores.\n\n
|
31
|
+
\ In MRI write program to run concurrently even not parallelly
|
32
|
+
is also important. \n\n This project is trying to help writing
|
33
|
+
concurrent program with ruby a little easier\n"
|
34
|
+
email:
|
35
|
+
- 24588062@qq.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- ".gitignore"
|
41
|
+
- Gemfile
|
42
|
+
- Gemfile.lock
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- lib/rbgo.rb
|
46
|
+
- lib/rbgo/corun.rb
|
47
|
+
- lib/rbgo/network_service.rb
|
48
|
+
- lib/rbgo/select_chan.rb
|
49
|
+
- lib/rbgo/version.rb
|
50
|
+
- lib/rbgo/wait_group.rb
|
51
|
+
- rbgo.gemspec
|
52
|
+
homepage: https://github.com/wangyin-git/select_chan
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata: {}
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubygems_version: 3.0.3
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: Write concurrent program with Ruby in Golang style
|
75
|
+
test_files: []
|