ruby-changes:64557
From: Koichi <ko1@a...>
Date: Thu, 24 Dec 2020 17:52:19 +0900 (JST)
Subject: [ruby-changes:64557] 8664c3ddef (master): update doc/ractor.md
https://git.ruby-lang.org/ruby.git/commit/?id=8664c3ddef From 8664c3ddef548b53c4f5c5264cccd24ae08f174f Mon Sep 17 00:00:00 2001 From: Koichi Sasada <ko1@a...> Date: Thu, 24 Dec 2020 17:41:48 +0900 Subject: update doc/ractor.md diff --git a/doc/ractor.md b/doc/ractor.md index 23b28ae..0ca3632 100644 --- a/doc/ractor.md +++ b/doc/ractor.md @@ -8,14 +8,14 @@ Ractor is designed to provide a parallel execution feature of Ruby without threa https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L8 You can make multiple Ractors and they run in parallel. -* Ractors run in parallel. +* `Ractor.new{ expr }` creates a new Ractor and `expr` is run in parallel on a parallel computer. * Interpreter invokes with the first Ractor (called *main Ractor*). * If main Ractor terminated, all Ractors receive terminate request like Threads (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). * Each Ractor has 1 or more Threads. - * Threads in a Ractor shares a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). + * Threads in a Ractor shares a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. * The overhead of creating a Ractor is similar to overhead of one Thread creation. -### Limited sharing +### Limited sharing between multiple ractors Ractors don't share everything, unlike threads. @@ -24,7 +24,8 @@ Ractors don't share everything, unlike threads. https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L24 * Immutable objects: frozen objects which don't refer to unshareable-objects. * `i = 123`: `i` is an immutable object. * `s = "str".freeze`: `s` is an immutable object. - * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refer unshareable-object `[2]` (which is not frozen). + * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers unshareable-object `[2]` (which is not frozen). + * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers Symbol `:c` and shareable `Object` class object which is not frozen. * Class/Module objects * Special shareable objects * Ractor object itself. @@ -35,27 +36,28 @@ Ractors don't share everything, unlike threads. https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L36 Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. There are two message exchange protocols: push type (message passing) and pull type. * Push type message passing: `Ractor#send(obj)` and `Ractor.receive()` pair. - * Sender ractor passes the `obj` to receiver Ractor. - * Sender knows a destination Ractor (the receiver of `r.send(obj)`) and the receiver does not know the sender (accept all message from any ractors). - * Receiver has infinite queue and sender enqueues the message. Sender doesn't block to put message. - * This type is based on actor model + * Sender ractor passes the `obj` to the ractor `r` by `r.send(obj)` and receiver ractor receives the message with `Ractor.receive`. + * Sender knows the destination Ractor `r` and the receiver does not know the sender (accept all message from any ractors). + * Receiver has infinite queue and sender enqueues the message. Sender doesn't block to put message into this queue. + * This type message exchangin is employed by many other Actor-based language. + * `Ractor.receive_if{ filter_expr }` is a variant of `Ractor.receive` to select a message. * Pull type communication: `Ractor.yield(obj)` and `Ractor#take()` pair. - * Sender ractor declare to yield the `obj` and receiver Ractor take it. - * Sender doesn't know a destination Ractor and receiver knows the sender (the receiver of `r.take`). + * Sender ractor declare to yield the `obj` by `Ractor.yield(obj)` and receiver Ractor take it with `r.take`. + * Sender doesn't know a destination Ractor and receiver knows the sender Ractor `r`. * Sender or receiver will block if there is no other side. ### Copy & Move semantics to send messages To send unshareable objects as messages, objects are copied or moved. -* Copy: use deep-copy (like dRuby) -* Move: move membership +* Copy: use deep-copy. +* Move: move membership. * Sender can not access the moved object after moving the object. * Guarantee that at least only 1 Ractor can access the object. ### Thread-safety -Ractor helps to write a thread-safe program, but we can make thread-unsafe programs with Ractors. +Ractor helps to write a thread-safe concurrent program, but we can make thread-unsafe programs with Ractors. * GOOD: Sharing limitation * Most objects are unshareable, so we can't make data-racy and race-conditional programs. @@ -68,18 +70,18 @@ Ractor helps to write a thread-safe program, but we can make thread-unsafe progr https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L70 * Some kind of shareable objects can introduce transactions (STM, for example). However, misusing transactions will generate inconsistent state. Without Ractor, we need to trace all of state-mutations to debug thread-safety issues. -With Ractor, you can concentrate to suspicious +With Ractor, you can concentrate to suspicious code which are shared with Ractors. ## Creation and termination ### `Ractor.new` -* `Ractor.new do expr end` generates another Ractor. +* `Ractor.new{ expr }` generates another Ractor. ```ruby # Ractor.new with a block creates new Ractor r = Ractor.new do - # This block will be run in parallel + # This block will be run in parallel with other ractors end # You can name a Ractor with `name:` argument. @@ -93,15 +95,11 @@ r.name #=> 'test-name' https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L95 ### Given block isolation The Ractor execute given `expr` in a given block. -Given block will be isolated from outer scope by `Proc#isolate`. +Given block will be isolated from outer scope by `Proc#isolate`. To prevent sharing unshareable objects between ractors, block outer-variables, `self` and other information are isolated. -```ruby -# To prevent sharing unshareable objects between ractors, -# block outer-variables, `self` and other information are isolated. -# Given block will be isolated by `Proc#isolate` method. -# `Proc#isolate` is called at Ractor creation timing (`Ractor.new` is called) -# and it can cause an error if block accesses outer variables. +Given block will be isolated by `Proc#isolate` method (not exposed yet for Ruby users). `Proc#isolate` is called at Ractor creation timing (`Ractor.new` is called). If given Proc object is not enable to isolate because of outer variables and so on, an error will be raised. +```ruby begin a = true r = Ractor.new do @@ -116,6 +114,7 @@ end https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L114 ```ruby r = Ractor.new do + p self.class #=> Ractor self.object_id end r.take == self.object_id #=> false @@ -177,18 +176,23 @@ end https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L176 ## Communication between Ractors -Communication between Ractors is achieved by sending and receiving messages. +Communication between Ractors is achieved by sending and receiving messages. There is two way to communicate each other. * (1) Message sending/receiving * (1-1) push type send/receive (sender knows receiver). similar to the Actor model. * (1-2) pull type yield/take (receiver knows sender). -* (2) Using shareable container objects (not implemented yet) +* (2) Using shareable container objects + * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) + * more? -Users can control blocking on (1), but should not control on (2) (only manage as critical section). +Users can control program execution timing with (1), but should not control with (2) (only manage as critical section). + +For message sending and receiving, there are two types APIs: push type and pull type. * (1-1) send/receive (push type) * `Ractor#send(obj)` (`Ractor#<<(obj)` is an aliases) send a message to the Ractor's incoming port. Incoming port is connected to the infinite size incoming queue so `Ractor#send` will never block. * `Ractor.receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor.receive` calling will block. + * `Ractor.receive_if{|msg| filter_expr }` is variant of `Ractor.receive`. `receive_if` only receives a message which `filter_expr` is true (So `Ractor.receive` is same as `Ractor.receive_if{ true }`. * (1-2) yield/take (pull type) * `Ractor.yield(obj)` send an message to a Ractor which are calling `Ractor#take` via outgoing port . If no Ractors are waiting for it, the `Ractor.yield(obj)` will block. If multiple Ractors are waiting for `Ractor.yield(obj)`, only one Ractor can receive the message. * `Ractor#take` receives a message which is waiting by `Ractor.yield(obj)` method from the specified Ractor. If the Ractor does not call `Ractor.yield` yet, the `Ractor#take` call will block. @@ -196,13 +200,13 @@ Users can control blocking on (1), but should not control on (2) (only manage as https://github.com/ruby/ruby/blob/trunk/doc/ractor.md#L200 * You can close the incoming port or outgoing port. * You can close then with `Ractor#close_incoming` and `Ractor#close_outgoing`. * If the incoming port is closed for a Ractor, you can't `send` to the Ractor. If `Ractor.receive` is blocked for the closed incoming port, then it will raise an exception. - * If the outgoing port is closed for a Ractor, you can't call `Ractor#take` and `Ractor.yield` on the Ractor. If `Ractor#take` is blocked for the Ractor, then it will raise an exception. + * If the outgoing port is closed for a Ractor, you can't call `Ractor#take` and `Ractor.yield` on the Ractor. If ractors are blocking by `Ractor#take` or `Ractor.yield` (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/