2
2
3
3
# Introduction
4
4
5
- Rust supports concurrency and parallelism through lightweight tasks.
6
- Rust tasks are significantly cheaper to create than traditional
7
- threads, with a typical 32-bit system able to run hundreds of
8
- thousands simultaneously. Tasks in Rust are what are often referred to
9
- as _ green threads_ , cooperatively scheduled by the Rust runtime onto a
10
- small number of operating system threads.
5
+ The Rust language is designed from the ground up to support pervasive
6
+ and safe concurrency through lightweight, memory-isolated tasks and
7
+ message passing.
8
+
9
+ Rust tasks are not the same as traditional threads - they are what are
10
+ often referred to as _ green threads_ , cooperatively scheduled by the
11
+ Rust runtime onto a small number of operating system threads. Being
12
+ significantly cheaper to create than traditional threads, Rust can
13
+ create hundreds of thousands of concurrent tasks on a typical 32-bit
14
+ system.
11
15
12
16
Tasks provide failure isolation and recovery. When an exception occurs
13
17
in rust code (either by calling ` fail ` explicitly or by otherwise performing
@@ -16,11 +20,11 @@ to `catch` an exception as in other languages. Instead tasks may monitor
16
20
each other to detect when failure has occurred.
17
21
18
22
Rust tasks have dynamically sized stacks. When a task is first created
19
- it starts off with a small amount of stack (in the hundreds to
20
- low thousands of bytes, depending on plattform), and more stack is
21
- added as needed. A Rust task will never run off the end of the stack as
22
- is possible in many other languages, but they do have a stack budget,
23
- and if a Rust task exceeds its stack budget then it will fail safely.
23
+ it starts off with a small amount of stack (currently in the low
24
+ thousands of bytes, depending on platform) and more stack is acquired as
25
+ needed. A Rust task will never run off the end of the stack as is
26
+ possible in many other languages, but they do have a stack budget, and
27
+ if a Rust task exceeds its stack budget then it will fail safely.
24
28
25
29
Tasks make use of Rust's type system to provide strong memory safety
26
30
guarantees, disallowing shared mutable state. Communication between
@@ -32,12 +36,12 @@ explore some typical patterns in concurrent Rust code, and finally
32
36
discuss some of the more exotic synchronization types in the standard
33
37
library.
34
38
35
- # A note about the libraries
39
+ ## A note about the libraries
36
40
37
41
While Rust's type system provides the building blocks needed for safe
38
42
and efficient tasks, all of the task functionality itself is implemented
39
43
in the core and standard libraries, which are still under development
40
- and do not always present a nice programming interface.
44
+ and do not always present a consistent interface.
41
45
42
46
In particular, there are currently two independent modules that provide
43
47
a message passing interface to Rust code: ` core::comm ` and ` core::pipes ` .
@@ -66,43 +70,96 @@ concurrency at the moment.
66
70
[ `std::arc` ] : std/arc.html
67
71
[ `std::par` ] : std/par.html
68
72
69
- # Spawning a task
73
+ # Basics
70
74
71
- Spawning a task is done using the various spawn functions in the
72
- module ` task ` . Let's begin with the simplest one, ` task::spawn() ` :
75
+ The programming interface for creating and managing tasks is contained
76
+ in the ` task ` module of the ` core ` library, making it available to all
77
+ Rust code by default. At it's simplest, creating a task is a matter of
78
+ calling the ` spawn ` function, passing a closure to run in the new
79
+ task.
73
80
74
81
~~~~
82
+ # use io::println;
75
83
use task::spawn;
76
- use io::println;
77
84
78
- let some_value = 22;
85
+ // Print something profound in a different task using a named function
86
+ fn print_message() { println("I am running in a different task!"); }
87
+ spawn(print_message);
88
+
89
+ // Print something more profound in a different task using a lambda expression
90
+ spawn( || println("I am also running in a different task!") );
79
91
92
+ // The canonical way to spawn is using `do` notation
80
93
do spawn {
81
- println(~"This executes in the child task.");
82
- println(fmt!("%d", some_value));
94
+ println("I too am running in a different task!");
83
95
}
84
96
~~~~
85
97
86
- The argument to ` task::spawn() ` is a [ unique
87
- closure] ( #unique-closures ) of type ` fn~() ` , meaning that it takes no
88
- arguments and generates no return value. The effect of ` task::spawn() `
89
- is to fire up a child task that will execute the closure in parallel
90
- with the creator.
98
+ In Rust, there is nothing special about creating tasks - the language
99
+ itself doesn't know what a 'task' is. Instead, Rust provides in the
100
+ type system all the tools necessary to implement safe concurrency,
101
+ _ owned types_ in particular, and leaves the dirty work up to the
102
+ core library.
103
+
104
+ The ` spawn ` function has a very simple type signature: `fn spawn(f:
105
+ ~ fn())`. Because it accepts only owned closures, and owned closures
106
+ contained only owned data, ` spawn ` can safely move the entire closure
107
+ and all its associated state into an entirely different task for
108
+ execution. Like any closure, the function passed to spawn may capture
109
+ an environment that it carries across tasks.
110
+
111
+ ~~~
112
+ # use io::println;
113
+ # use task::spawn;
114
+ # fn generate_task_number() -> int { 0 }
115
+ // Generate some state locally
116
+ let child_task_number = generate_task_number();
117
+
118
+ do spawn {
119
+ // Capture it in the remote task
120
+ println(fmt!("I am child number %d", child_task_number));
121
+ }
122
+ ~~~
123
+
124
+ By default tasks will be multiplexed across the available cores, running
125
+ in parallel, thus on a multicore machine, running the following code
126
+ should interleave the output in vaguely random order.
127
+
128
+ ~~~
129
+ # use io::print;
130
+ # use task::spawn;
131
+
132
+ for int::range(0, 20) |child_task_number| {
133
+ do spawn {
134
+ print(fmt!("I am child number %d\n", child_task_number));
135
+ }
136
+ }
137
+ ~~~
138
+
139
+ ## Communication
91
140
92
- # Communication
141
+ Now that we have spawned a new task, it would be nice if we could
142
+ communicate with it. Recall that Rust does not have shared mutable
143
+ state, so one task may not manipulate variables owned by another task.
144
+ Instead we use * pipes* .
93
145
94
- Now that we have spawned a child task, it would be nice if we could
95
- communicate with it. This is done using * pipes* . Pipes are simply a
96
- pair of endpoints, with one for sending messages and another for
97
- receiving messages. The easiest way to create a pipe is to use
98
- ` pipes::stream ` . Imagine we wish to perform two expensive
99
- computations in parallel. We might write something like:
146
+ Pipes are simply a pair of endpoints, with one for sending messages
147
+ and another for receiving messages. Pipes are low-level communication
148
+ building-blocks and so come in a variety of forms, appropriate for
149
+ different use cases, but there are just a few varieties that are most
150
+ commonly used, which we will cover presently.
151
+
152
+ The simplest way to create a pipe is to use the ` pipes::stream `
153
+ function to create a ` (Chan, Port) ` pair. In Rust parlance a 'channel'
154
+ is a sending endpoint of a pipe, and a 'port' is the recieving
155
+ endpoint. Consider the following example of performing two calculations
156
+ concurrently.
100
157
101
158
~~~~
102
159
use task::spawn;
103
160
use pipes::{stream, Port, Chan};
104
161
105
- let (chan, port) = stream();
162
+ let (chan, port): (Chan<int>, Port<int>) = stream();
106
163
107
164
do spawn {
108
165
let result = some_expensive_computation();
@@ -111,43 +168,45 @@ do spawn {
111
168
112
169
some_other_expensive_computation();
113
170
let result = port.recv();
114
-
115
171
# fn some_expensive_computation() -> int { 42 }
116
172
# fn some_other_expensive_computation() {}
117
173
~~~~
118
174
119
- Let's walk through this code line-by-line. The first line creates a
120
- stream for sending and receiving integers:
175
+ Let's examine this example in detail. The ` let ` statement first creates a
176
+ stream for sending and receiving integers (recall that ` let ` can be
177
+ used for destructuring patterns, in this case separating a tuple into
178
+ its component parts).
121
179
122
- ~~~~ {.ignore}
123
- # use pipes::stream;
124
- let (chan, port) = stream();
180
+ ~~~~
181
+ # use pipes::{ stream, Chan, Port} ;
182
+ let (chan, port): (Chan<int>, Port<int>) = stream();
125
183
~~~~
126
184
127
- This port is where we will receive the message from the child task
128
- once it is complete. The channel will be used by the child to send a
129
- message to the port. The next statement actually spawns the child:
185
+ The channel will be used by the child task to send data to the parent task,
186
+ which will wait to recieve the data on the port. The next statement
187
+ spawns the child task.
130
188
131
189
~~~~
132
190
# use task::{spawn};
133
- # use comm::{Port, Chan};
191
+ # use task::spawn;
192
+ # use pipes::{stream, Port, Chan};
134
193
# fn some_expensive_computation() -> int { 42 }
135
- # let port = Port();
136
- # let chan = port.chan();
194
+ # let (chan, port) = stream();
137
195
do spawn {
138
196
let result = some_expensive_computation();
139
197
chan.send(result);
140
198
}
141
199
~~~~
142
200
143
- This child will perform the expensive computation send the result
144
- over the channel. (Under the hood, ` chan ` was captured by the
145
- closure that forms the body of the child task. This capture is
146
- allowed because channels are sendable.)
201
+ Notice that ` chan ` was transferred to the child task implicitly by
202
+ capturing it in the task closure. Both ` Chan ` and ` Port ` are sendable
203
+ types and may be captured into tasks or otherwise transferred between
204
+ them. In the example, the child task performs an expensive computation
205
+ then sends the result over the captured channel.
147
206
148
- Finally, the parent continues by performing
149
- some other expensive computation and then waiting for the child's result
150
- to arrive on the port:
207
+ Finally, the parent continues by performing some other expensive
208
+ computation and then waiting for the child's result to arrive on the
209
+ port:
151
210
152
211
~~~~
153
212
# use pipes::{stream, Port, Chan};
@@ -158,7 +217,91 @@ some_other_expensive_computation();
158
217
let result = port.recv();
159
218
~~~~
160
219
161
- # Creating a task with a bi-directional communication path
220
+ The ` Port ` and ` Chan ` pair created by ` stream ` enable efficient
221
+ communication between a single sender and a single receiver, but
222
+ multiple senders cannot use a single ` Chan ` , nor can multiple
223
+ receivers use a single ` Port ` . What if our example needed to perform
224
+ multiple computations across a number of tasks? The following cannot
225
+ be written:
226
+
227
+ ~~~ {.xfail-test}
228
+ # use task::{spawn};
229
+ # use pipes::{stream, Port, Chan};
230
+ # fn some_expensive_computation() -> int { 42 }
231
+ let (chan, port) = stream();
232
+
233
+ do spawn {
234
+ chan.send(some_expensive_computation());
235
+ }
236
+
237
+ // ERROR! The previous spawn statement already owns the channel,
238
+ // so the compiler will not allow it to be captured again
239
+ do spawn {
240
+ chan.send(some_expensive_computation());
241
+ }
242
+ ~~~
243
+
244
+ Instead we can use a ` SharedChan ` , a type that allows a single
245
+ ` Chan ` to be shared by multiple senders.
246
+
247
+ ~~~
248
+ # use task::spawn;
249
+ use pipes::{stream, SharedChan};
250
+
251
+ let (chan, port) = stream();
252
+ let chan = SharedChan(move chan);
253
+
254
+ for uint::range(0, 3) |init_val| {
255
+ // Create a new channel handle to distribute to the child task
256
+ let child_chan = chan.clone();
257
+ do spawn {
258
+ child_chan.send(some_expensive_computation(init_val));
259
+ }
260
+ }
261
+
262
+ let result = port.recv() + port.recv() + port.recv();
263
+ # fn some_expensive_computation(_i: uint) -> int { 42 }
264
+ ~~~
265
+
266
+ Here we transfer ownership of the channel into a new ` SharedChan `
267
+ value. Like ` Chan ` , ` SharedChan ` is a non-copyable, owned type
268
+ (sometimes also referred to as an 'affine' or 'linear' type). Unlike
269
+ ` Chan ` though, ` SharedChan ` may be duplicated with the ` clone() `
270
+ method. A cloned ` SharedChan ` produces a new handle to the same
271
+ channel, allowing multiple tasks to send data to a single port.
272
+ Between ` spawn ` , ` stream ` and ` SharedChan ` we have enough tools
273
+ to implement many useful concurrency patterns.
274
+
275
+ Note that the above ` SharedChan ` example is somewhat contrived since
276
+ you could also simply use three ` stream ` pairs, but it serves to
277
+ illustrate the point. For reference, written with multiple streams it
278
+ might look like the example below.
279
+
280
+ ~~~
281
+ # use task::spawn;
282
+ # use pipes::{stream, Port, Chan};
283
+
284
+ // Create a vector of ports, one for each child task
285
+ let ports = do vec::from_fn(3) |init_val| {
286
+ let (chan, port) = stream();
287
+ do spawn {
288
+ chan.send(some_expensive_computation(init_val));
289
+ }
290
+ port
291
+ };
292
+
293
+ // Wait on each port, accumulating the results
294
+ let result = ports.foldl(0, |accum, port| *accum + port.recv() );
295
+ # fn some_expensive_computation(_i: uint) -> int { 42 }
296
+ ~~~
297
+
298
+ # Unfinished notes
299
+
300
+ ## Actor patterns
301
+
302
+ ## Linearity, option dancing, owned closures
303
+
304
+ ## Creating a task with a bi-directional communication path
162
305
163
306
A very common thing to do is to spawn a child task where the parent
164
307
and child both need to exchange messages with each other. The
@@ -227,3 +370,4 @@ assert from_child.recv() == ~"0";
227
370
228
371
The parent task first calls ` DuplexStream ` to create a pair of bidirectional endpoints. It then uses ` task::spawn ` to create the child task, which captures one end of the communication channel. As a result, both parent
229
372
and child can send and receive data to and from the other.
373
+
0 commit comments