Skip to content

Commit d0ec60d

Browse files
authored
More doc improvements to ractor.md (ruby#15676)
[DOC] More doc improvements to ractor.md
1 parent 37b98f0 commit d0ec60d

File tree

1 file changed

+61
-73
lines changed

1 file changed

+61
-73
lines changed

doc/language/ractor.md

Lines changed: 61 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,27 @@ Ractors are designed to provide parallel execution of Ruby code without thread-s
44

55
## Summary
66

7-
### Multiple Ractors in an interpreter process
7+
### Multiple Ractors in a ruby process
88

9-
You can create multiple Ractors which can run ruby code in parallel.
9+
You can create multiple Ractors which can run ruby code in parallel with each other.
1010

1111
* `Ractor.new{ expr }` creates a new Ractor and `expr` can run in parallel with other ractors on a multi-core computer.
12-
* Ruby processes start with one Ractor (called the *main Ractor*).
13-
* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave.
14-
* Each Ractor contains one or more Threads.
15-
* Threads within the same Ractor share a Ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel.
16-
* The overhead of creating a Ractor is slightly above the overhead of creating a Thread.
12+
* Ruby processes start with one ractor (called the *main ractor*).
13+
* If the main ractor terminates, all other ractors receive termination requests, similar to how threads behave.
14+
* Each Ractor contains one or more `Thread`s.
15+
* Threads within the same ractor share a ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel.
16+
* The overhead of creating a ractor is slightly above the overhead of creating a thread.
1717

1818
### Limited sharing between Ractors
1919

20-
Ractors don't share all objects, unlike Threads which can access any object other than objects stored in another Thread's thread-locals.
20+
Ractors don't share all objects, unlike threads which can access any object other than objects stored in another thread's thread-locals.
2121

22-
* Most objects are *Unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across Ractors.
23-
* Some objects are *Shareable objects*. Here is an incomplete list to give you an idea:
24-
* Immutable objects: these are frozen objects which don't refer to unshareable-objects.
25-
* `i = 123`: `i` is an immutable object.
26-
* `s = "str".freeze`: `s` is an immutable object.
27-
* `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers to the unshareable-object `[2]` (which is not frozen).
28-
* `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers to the Symbol `:c` and the shareable `Object` class.
22+
* Most objects are *unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across ractors.
23+
* Some objects are *shareable objects*. Here is an incomplete list to give you an idea:
24+
* `i = 123`: All `Integer`s are shareable.
25+
* `s = "str".freeze`: Frozen strings are shareable if they have no instance variables that refer to unshareable objects.
26+
* `a = [1, [2], 3].freeze`: `a` is not a shareable object because `a` refers to the unshareable object `[2]` (this Array is not frozen).
27+
* `h = {c: Object}.freeze`: `h` is shareable because `Symbol`s and `Class`es are shareable, and the Hash is frozen.
2928
* Class/Module objects are always shareable, even if they refer to unshareable objects.
3029
* Special shareable objects
3130
* Ractor objects themselves are shareable.
@@ -53,17 +52,17 @@ All Ractors have a default port, which `Ractor#send`, `Ractor.receive` (etc) wil
5352

5453
To send unshareable objects to another ractor, objects are either copied or moved.
5554

56-
* Copy: deep-copies the object to the other ractor.
55+
* Copy: deep-copies the object to the other ractor. All unshareable objects will be `Kernel#clone`ed.
5756
* Move: moves membership to another ractor.
58-
* The sending Ractor can not access the moved object after it moves.
59-
* There is a guarantee that only one Ractor can access an unshareable object at once.
57+
* The sending ractor can not access the moved object after it moves.
58+
* There is a guarantee that only one ractor can access an unshareable object at once.
6059

6160
### Thread-safety
6261

6362
Ractors help to write thread-safe, concurrent programs. They allow sharing of data only through explicit message passing for
6463
unshareable objects. Shareable objects are guaranteed to work correctly across ractors, even if the ractors are running in parallel.
65-
This guarantee, however, only applies across ractors. You still need to use Mutexes and other thread-safety tools within a ractor if
66-
you're using multiple ruby Threads.
64+
This guarantee, however, only applies across ractors. You still need to use `Mutex`es and other thread-safety tools within a ractor if
65+
you're using multiple ruby `Thread`s.
6766

6867
* Most objects are unshareable. You can't create data-races across ractors due to the inability to use these objects across ractors.
6968
* Shareable objects are protected by locks (or otherwise don't need to be) so they can be used by more than one ractor at once.
@@ -72,7 +71,7 @@ you're using multiple ruby Threads.
7271

7372
### `Ractor.new`
7473

75-
* `Ractor.new{ expr }` creates a Ractor.
74+
* `Ractor.new { expr }` creates a Ractor.
7675

7776
```ruby
7877
# Ractor.new with a block creates a new Ractor
@@ -84,7 +83,6 @@ end
8483
r = Ractor.new name: 'my-first-ractor' do
8584
end
8685

87-
# and Ractor#name returns its name.
8886
r.name #=> 'my-first-ractor'
8987
```
9088

@@ -164,40 +162,41 @@ end
164162

165163
## Communication between Ractors
166164

167-
Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate:
165+
Communication between ractors is achieved by sending and receiving messages. There are two ways to communicate:
168166

169167
* (1) Sending and receiving messages via `Ractor::Port`
170-
* (2) Using shareable container objects
171-
* Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar))
168+
* (2) Using shareable container objects. For example, the Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar))
172169

173170
Users can control program execution timing with (1), but should not control with (2) (only perform critical sections).
174171

175172
For sending and receiving messages, these are the fundamental APIs:
176173

177174
* send/receive via `Ractor::Port`.
178-
* `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so it will never block the caller.
179-
* `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread.
175+
* `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so sending will never block the caller.
176+
* `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread until a message is sent.
180177
* `Ractor#send` and `Ractor.receive` use ports (their default port) internally, so are conceptually similar to the above.
181178
* You can close a `Ractor::Port` by `Ractor::Port#close`. A port can only be closed by the ractor that created it.
182179
* If a port is closed, you can't `send` to it. Doing so raises an exception.
183-
* When a Ractor is terminated, the Ractor's ports are automatically closed.
180+
* When a ractor is terminated, the ractor's ports are automatically closed.
184181
* You can wait for a ractor's termination and receive its return value with `Ractor#value`. This is similar to `Thread#value`.
185182

186183
There are 3 ways to send an object as a message:
187184

188185
1) Send a reference: sending a shareable object sends only a reference to the object (fast).
186+
189187
2) Copy an object: sending an unshareable object through copying it deeply (can be slow). Note that you can not send an object this way which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported.
188+
190189
3) Move an object: sending an unshareable object across ractors with a membership change. The sending Ractor can not access the moved object after moving it, otherwise an exception will be raised. Implementation note: `T_DATA` objects are not supported.
191190

192191
You can choose between "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)`. The default is `false` ("Copy"). However, if the object is shareable it will automatically use `move`.
193192

194193
### Wait for multiple Ractors with `Ractor.select`
195194

196195
You can wait for messages on multiple ports at once.
197-
The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message.
196+
The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is the received message.
198197

199-
To make it convenient, `Ractor.select` can also accept Ractors. In this case, it waits for their termination.
200-
The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's block.
198+
To make it convenient, `Ractor.select` can also accept ractors. In this case, it waits for their termination.
199+
The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of the ractor's block.
201200

202201
Wait for a single ractor (same as `Ractor#value`):
203202

@@ -208,36 +207,33 @@ r, obj = Ractor.select(r1)
208207
r == r1 and obj == 'r1' #=> true
209208
```
210209

211-
Waiting for two ractors:
210+
Wait for two ractors:
212211

213212
```ruby
214213
r1 = Ractor.new{'r1'}
215214
r2 = Ractor.new{'r2'}
216215
rs = [r1, r2]
217216
values = []
218217

219-
# Wait for r1 or r2's termination
220-
r, obj = Ractor.select(*rs)
221-
rs.delete(r)
222-
values << obj
218+
while rs.any?
219+
r, obj = Ractor.select(*rs)
220+
rs.delete(r)
221+
values << obj
222+
end
223223

224-
# Second try (rs only contain not-closed ractors)
225-
r, obj = Ractor.select(*rs)
226-
rs.delete(r)
227-
values << obj
228224
values.sort == ['r1', 'r2'] #=> true
229225
```
230226

231227
NOTE: Using `Ractor.select()` on a very large number of ractors has the same issue as `select(2)` currently.
232228

233-
### Closing Ractor's ports
229+
### Closing ports
234230

235-
* `Ractor::Port#close` close the ports (similar to `Queue#close`).
236-
* `port.send(obj)` where `port` is closed, will raise an exception.
231+
* `Ractor::Port#close` closes the port (similar to `Queue#close`).
232+
* `port.send(obj)` will raise an exception when the port is closed.
237233
* When the queue connected to the port is empty and port is closed, `Ractor::Port#receive` raises an exception. If the queue is not empty, it dequeues an object without exceptions.
238234
* When a Ractor terminates, the ports are closed automatically.
239235

240-
Example (try to get a result from closed Ractor):
236+
Example (try to get a result from closed ractor):
241237

242238
```ruby
243239
r = Ractor.new do
@@ -249,29 +245,27 @@ r.value # success (will return 'finish')
249245
# The ractor's termination value has already been given to another ractor
250246
Ractor.new r do |r|
251247
r.value #=> Ractor::Error
252-
end
248+
end.join
253249
```
254250

255-
Example (try to send to closed (terminated) Ractor):
251+
Example (try to send to closed port):
256252

257253
```ruby
258254
r = Ractor.new do
259255
end
260256

261-
r.join # wait for termination
257+
r.join # wait for termination, closes default port
262258

263259
begin
264260
r.send(1)
265261
rescue Ractor::ClosedError
266262
'ok'
267-
else
268-
'ng'
269263
end
270264
```
271265

272266
### Send a message by copying
273267

274-
`Ractor::Port#send(obj)` copy `obj` deeply if `obj` is an unshareable object.
268+
`Ractor::Port#send(obj)` copies `obj` deeply if `obj` is an unshareable object.
275269

276270
```ruby
277271
obj = 'str'.dup
@@ -299,23 +293,22 @@ end
299293
### Send a message by moving
300294

301295
`Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor.
302-
If the source Ractor touches the moved object (for example, calls a method like `obj.foo()`), it will raise an error.
296+
If the source ractor uses the moved object (for example, calls a method like `obj.foo()`), it will raise an error.
303297

304298
```ruby
305-
# move with Ractor#send
306299
r = Ractor.new do
307300
obj = Ractor.receive
308301
obj << ' world'
309302
end
310303

311304
str = 'hello'.dup
312305
r.send str, move: true
313-
# str is now moved, and accessing str from this Ractor is prohibited
306+
# str is now moved, and accessing str from this ractor is prohibited
314307
modified = r.value #=> 'hello world'
315308

316309

317310
begin
318-
# Error because it touches moved str.
311+
# Error because it uses moved str.
319312
str << ' exception' # raise Ractor::MovedError
320313
rescue Ractor::MovedError
321314
modified #=> 'hello world'
@@ -338,19 +331,19 @@ Once an object has been moved, the source object's class is changed to `Ractor::
338331

339332
The following is an inexhaustive list of shareable objects:
340333

341-
* Small integers, big integers, `Float`, `Complex`, `Rational`
342-
* All symbols, frozen Strings, `true`, `false`, `nil`
334+
* `Integer`, `Float`, `Complex`, `Rational`
335+
* `Symbol`, frozen `String` objects that don't refer to unshareables, `true`, `false`, `nil`
343336
* `Regexp` objects, if they have no instance variables or their instance variables refer only to shareables
344-
* Class and Module objects
345-
* `Ractor` and other special objects which care about synchronization
337+
* `Class` and `Module` objects
338+
* `Ractor` and other special objects which deal with synchronization
346339

347340
To make objects shareable, `Ractor.make_shareable(obj)` is provided. It tries to make the object shareable by freezing `obj` and recursively traversing its references to freeze them all. This method accepts the `copy:` keyword (default value is false). `Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. `Ractor.make_shareable(copy: false)` has no effect on an already shareable object. If the object cannot be made shareable, a `Ractor::Error` exception will be raised.
348341

349-
## Language changes to isolate unshareable objects between Ractors
342+
## Language changes to limit sharing between Ractors
350343

351-
To isolate unshareable objects between Ractors, we introduced additional language semantics on multi-Ractor Ruby programs.
344+
To isolate unshareable objects across ractors, we introduced additional language semantics for multi-ractor Ruby programs.
352345

353-
Note that without using Ractors, these additional semantics are not needed (100% compatible with Ruby 2).
346+
Note that when not using ractors, these additional semantics are not needed (100% compatible with Ruby 2).
354347

355348
### Global variables
356349

@@ -369,11 +362,11 @@ rescue Ractor::RemoteError => e
369362
end
370363
```
371364

372-
Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are Ractor-local. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details.
365+
Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are local to each ractor. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details.
373366

374367
### Instance variables of shareable objects
375368

376-
Instance variables of classes/modules can be accessed from non-main Ractors only if their values are shareable objects.
369+
Instance variables of classes/modules can be accessed from non-main ractors only if their values are shareable objects.
377370

378371
```ruby
379372
class C
@@ -514,15 +507,13 @@ The `shareable_constant_value` directive accepts the following modes (descriptio
514507
* experimental_everything: replaced to `CONST = Ractor.make_shareable(expr)`.
515508
* experimental_copy: replaced to `CONST = Ractor.make_shareable(expr, copy: true)`.
516509

517-
Except for the `none` mode (default), it is guaranteed that the constants in the file refer only to shareable objects.
510+
Except for the `none` mode (default), it is guaranteed that these constants refer only to shareable objects.
518511

519-
See [doc/syntax/comments.rdoc](syntax/comments.rdoc) for more details.
512+
See [syntax/comments.rdoc](../syntax/comments.rdoc) for more details.
520513

521514
### Shareable procs
522515

523-
Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the Proc's block is isolated
524-
from its outer environment, so it cannot access locals from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize
525-
the value to a different shareable object.
516+
Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the proc's block is isolated from its outer environment, so it cannot access variables from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize the value to a different shareable object.
526517

527518
```ruby
528519
p = Ractor.shareable_proc { p self }
@@ -538,10 +529,7 @@ rescue Ractor::IsolationError
538529
end
539530
```
540531

541-
In order to dynamically define a method with `define_method` that can be used from different ractors, you must
542-
define it with a shareable proc. Alternatively, you can use `class_eval` or `module_eval` with a String. Even though
543-
the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the
544-
method.
532+
In order to dynamically define a method with `Module#define_method` that can be used from different ractors, you must define it with a shareable proc. Alternatively, you can use `Module#class_eval` or `Module#module_eval` with a String. Even though the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the method.
545533

546534
```ruby
547535
class A
@@ -555,7 +543,7 @@ Ractor.new do
555543
end.join
556544
```
557545

558-
This isolation must be done to prevent the method from accessing captured outer variables across Ractors.
546+
This isolation must be done to prevent the method from accessing and assigning captured outer variables across ractors.
559547

560548
### Ractor-local storage
561549

0 commit comments

Comments
 (0)