rbgo 0.1.0

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
+ SHA256:
3
+ metadata.gz: d9e43797ac2188d0ed30432699def2789333d41d5dbacfacbc34710af4f1c505
4
+ data.tar.gz: 36b4745d925d33b30c759295d36337de1b283668959f922149d02e5fbc8c0738
5
+ SHA512:
6
+ metadata.gz: ca341dd5a7f430802ca235d01906ef67b801238c5566e1f415838c742ad28e10ac6d28862fa8bfde87f688bf2ada43a5bfd3fc21603f0933df8b8c620c3080e0
7
+ data.tar.gz: 023e7bbcbb60fff0b8768b773a0176753af86bf72fa970d48410a17ffe8dcd7f6d8bcf7b43d7ca77760bbf581a097cb1ae7481dd31a00dc7078c4b66a2725f3f
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ main.rb
2
+ .idea
3
+ pkg/*
4
+ *.gem
5
+ .bundle
6
+ tmp/*
7
+ .rbx
8
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rbgo (0.1.0)
5
+ sys-cpu (~> 0.8)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ ffi (1.11.1)
11
+ sys-cpu (0.8.0)
12
+ ffi
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ rbgo!
19
+
20
+ BUNDLED WITH
21
+ 2.0.2
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
@@ -0,0 +1,3 @@
1
+ module Rbgo
2
+ VERSION = "0.1.0"
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ require_relative 'rbgo/corun'
2
+ require_relative 'rbgo/select_chan'
3
+ require_relative 'rbgo/wait_group'
4
+ require_relative 'rbgo/network_service'
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: []