Previous | Table of Contents | Next |
The previous section of this tutorial explored Rust's tasks. Tasks as we have seen them don't really do well with sharing data. There are often instances where we may want multiple tasks spawned which reference, or even modify, some data. Enter the Arc module.
The
extra::arc module
is a wrapper for any data type to be shared between tasks. An
Arc<T>
is a shared immutable state. When spawning tasks that want to reference the same data, using the
clone()
function creates a new copy of the Arc that can be sent to a task via a
(Port<Arc>, Chan<Arc>)
pair. The underlying data is not changed, however. This allows sending a new arc to a task, and having it access the same data. Once within the spawned task, the
get()
function retreives the value wrapped by the Arc. These Arcs can only hold immutable data. We will be looking at a variation of the Arc that allows mutability in a bit. The following bit of code creates a vector of integers, wraps them in an Arc, and then prints out each value within a separate task.
1 2 3 4 5 6 7 8 9 10 11 12 13 | let nums = [1,78,3,5,-2,5,7,-11]; let numArc = Arc::new(nums); for i in range(0, nums.len()) { let (port, chan) = Chan::new(); chan.send(numArc.clone()); spawn(proc() { let taskArc = port.recv(); let taskNums = taskArc.get(); println!("{:d}", taskNums[i]); }); } |
phead3 Reading and Writing with RWArcs
Often if we are sharing data across many tasks, we will want to manipulate the data within the task in some manner. The Arc structure, however, has no actual method of accomplishing this. There is an alternative Arc, however called the
RWArc<T>
. A RWArc has a
read(|&T| {block})
and
write(|&T| {block})
function. The variable provided for the
&T
portion of this call is a borrowed reference to the underlying data of the RWArc. The read function serves similar to using a standard Arc (not RWArc), and provides for simultaneous cocurrent access to the data within the RWArc. Below is an identical implementation of the Arc example, except using a RWArc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let nums = [1,78,3,5,-2,5,7,-11]; let numArc = RWArc::new(nums); for i in range(0, nums.len()) { let (port, chan) = Chan::new(); chan.send(numArc.clone()); spawn(proc() { let taskArc = port.recv(); taskArc.read(|taskNums| { println!("{:d}", taskNums[i]); }); }); } |
When write is called, an RWArc flags the underlying data as being accessed and prevents any other write functions from continuing until the one currently accessing the data is complete. The below is a simple example of incrementing a number throughout multiple tasks using RWArc write functions, and then calling the collatz function (modified from the one in Part 1 of this tutorial to not overflow a task's stack through recursive calls) on the retrieved value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | extern mod extra; use extra::arc::RWArc; fn main() { let num = 0; let numArc = RWArc::new(num); for i in range(0, 50000) { let (port, chan) = Chan::new(); chan.send(numArc.clone()); spawn(proc() { let taskArc = port.recv(); let mut newNum = 0; taskArc.write(|taskNum| { *taskNum += 1; newNum = *taskNum; }); let collatzN = collatz(newNum); println!("Collatz of {:d} = {:d}", newNum, collatzN); }); } } fn collatz(N: int) -> int { let mut nLoc = N; let mut out = 0; while (nLoc != 1) { out += 1; match nLoc % 2 { 0 => {nLoc = nLoc/2;} _ => {nLoc = nLoc*3+1; } } } return out; } |
If for whatever reason a failure occurs during the read or write block of a RWArc, the RWArc will be exited and flagged as "poisoned." This causes any other read or write functions on the RWArc to immediately fail, thus preserving Rust's goal of cocurrent safety.