Congratulations on the scheduled release of Ruby 3.0. 3.0 introduced a feature called Ractor that enables parallel and parallel programming in Ruby. Regarding Ractor, Qiita already has a Good article. However, the level is a little too high for me, who is a completely self-taught amateur in programming, so the following is just a note of a little more rudimentary part for myself. I hope it helps others.
The version of Ruby is ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux].
You cannot reference local variables outside of Ractor.
i = 0
Ractor.new {
i
}.take
#=><internal:ractor>:267:in `new': can not isolate a Proc because it accesses outer variables (i). (ArgumentError)
If you want to pass a local variable, you can pass it as an argument to Ractor.new
.
i = 0
Ractor.new(i) {|j|
puts j #=>0
}.take
However, you can refer to the constants.
L = 0
Ractor.new {
puts L #=>0
}.take
You can also pass an instance of the class.
class Counter
def initialize
@value = 0
end
attr_reader :value
def inc
@value += 1
end
end
c = Counter.new
r = Ractor.new(c) {|counter|
10.times { counter.inc }
counter.value
}
puts r.take #=>10
There was an image that mutable objects (destructible objects) could not be used in Ractor. So are Arrays and Strings bad?
ary = [1, 2, "3"]
str = "Ruby"
Ractor.new(ary, str) {|a, s|
p a #=>[1, 2, "3"]
p s #=>"Ruby"
}.take
That doesn't seem to be the case. It seems that destructive operation is also possible.
str = "Ruby"
Ractor.new(str) {|s|
s << "!"
p s #=>"Ruby!"
}.take
The following seems to be okay.
str = "Ruby"
p 3.times.map {
Ractor.new(str) {|s|
s << ["!", "?"].sample
s
}
}.map(&:take) #=>["Ruby?", "Ruby!", "Ruby?"]
p str #=>"Ruby"
In other words, it seems that the str
ofnew (str)
is duplicated and goes into the block variable s
.
This seems to be the same for instances of homebrew classes.
c = Counter.new
p 4.times.map {
Ractor.new(c) {|counter|
counter.inc
counter.value
}
}.map(&:take) #=>[1, 1, 1, 1]
p c.value #=>0
The array doesn't seem to look like [1, 2, 3, 4]
. Instance c
is not shared.
Classes can be referenced even if they are defined outside.
class A
def sum(ary)
ary.sum
end
end
p Ractor.new {
a = A.new
a.sum([*1..10])
}.take #=>55
So, of course, you can also refer to top-level methods.
def sum(ary)
ary.sum
end
p Ractor.new {
sum([*1..10])
}.take #=>55
Or rather, I wrote "naturally", but what is self in Ractor?
Ractor.new {
p self #=>#<Ractor:#2 ractor_sample.rb:1 running>
p self.class #=>Ractor
p self.class.ancestors #=>[Ractor, Object, Kernel, BasicObject]
}.take
It seems that the classes defined inside Ractor can also be used outside.
Ractor.new {
class A
def rand
Random.rand(10)
end
end
p A.new.rand #=>7
}.take
p A.new.rand #=>1
But maybe this just happens to be working ...
Proc, Object#method
It looks like Proc can't be passed to Ractor ... I'm sorry.
f = ->(x) { x * 3 }
Ractor.new(f) {|func|
(1..4).map(&func)
}.take
#=><internal:ractor>:267:in `new': allocator undefined for Proc (TypeError)
Then this is also useless, isn't it?
def f(x) = x * 3
Ractor.new(method(:f)) {|func|
(1..4).map(&func)
}.take
#=><internal:ractor>:267:in `new': allocator undefined for Method (TypeError)
Do you want to give up ...
def f(x) = x * 3
Ractor.new {
p (1..4).map(&method(:f)) #=>[3, 6, 9, 12]
}.take
Well, I don't think it's necessary to force it to look like functional programming in Ruby, but it's a bit disappointing. By the way, I used "Endless method definition" (1 line def) here.
Use Ractor.yield and Ractor # take. Ractor.yield waits for an external Ractor # take and then takes the value. The return value of Ractor is automatically Ractor.yield. Error if you take extra. Will Ractor.yield that is not taken be thrown away?
r = Ractor.new do
Ractor.yield 1
Ractor.yield 2
3
end
p r.take #=>1
sleep(1)
p r.take #=>(1 second later) 2
p r.take #=>3
p r.take #=><internal:ractor>:694:in `take': The outgoing-port is already closed (Ractor::ClosedError)
If you take it before Ractor.yield, wait until it is yielded.
r = Ractor.new do
sleep(1)
Ractor.yield 1
end
p r.take #=>(1 second later) 1
That is, it will not be evaluated until it is needed (Ractor # taken). It's kind of like "lazy evaluation".
Use Ractor # send to plunge into a Queue in Ractor and Ractor.receive to retrieve it from the Queue. Block if the Queue is empty.
r = Ractor.new do
loop do
p Ractor.receive
end
end
r.send(1)
r.send(2)
sleep(1)
r.send(3)
r.take
Doing this will print 1, 2 first, then 3 after 1 second and then freeze. Since there is a Queue inside, you can store the input by Ractor # send.
Push + Pull
Example of concurrency
r = Ractor.new do
a = Ractor.receive
#(Processing using something a)
#(Return value)
end
#r and do_Process something in parallel
r.send(data)
do_something
result = r.take
I wonder if r
can be reused this way.
r = Ractor.new do
loop do
a = Ractor.receive
#(Processing using something a)
Ractor.yield result #Return value
end
end
It's a primality test from 1 to 1000 ...
require 'prime'
N = 1000
RN = 10
pipe = Ractor.new do
loop do
Ractor.yield Ractor.receive
end
end
workers = (1..RN).map do
Ractor.new(pipe) do |pipe|
loop do
n = pipe.take
Ractor.yield [n, n.prime?]
end
end
end
(1..N).each { |i| pipe.send(i) }
pp (1..N).map {
r, (n, b) = Ractor.select(*workers)
[n, b]
}.sort_by { |(n, b)| n }
You can pass another Ractor to the Ractor.
pipe
Ispipe.send
Store what was done in the internal Queue,pipe.take
があるまで待って流します。ただここでIs先にworker
In the definition ofpipe.take
Because it has been done laterpipe.send
Wait for youpipe.take
することになります。これIsどちらが先でもいいので、(1..N).each { |i| pipe.send(i) }
Toworker
You can also bring it before the definition of.
A total of RN
s are created for workers
. In other words, it is parallel processing of 1 pipe
+ RN
.
N
data is poured into pipe
. It is distributed to RN
number of workers
and processed.
Each worker
waits for the result Ractor.yield
when it's done. Receive them with Ractor.select (* workers)
in the order in which they were created. The passed worker
will repeat the same process with loop
.
It's kind of like "lazy evaluation".
The following was very helpful.
Please point out any mistakes.
Recommended Posts