Sync and Send

There are two key traits that the parallel Rust programmer must understand—Send and Sync. The Send trait is applied to types that can be transferred across thread boundaries. That is, any type T: Send is safe to move from one thread to another. Rc<T>: !Send means that it is explicitly marked as being unsafe to move between threads. Why? Consider if it were marked Send; what would happen? We know from the previous chapter that Rc<T> is a boxed T with two counters in place for weak and strong references to T. These counters are the trick. Suppose we spread an Rc<T> across two threads—call them A and B—each of which created references, dropped them, and the like. What would happen if both A and B dropped the last reference to Rc<T> at the same time? We have a race between either thread to see which can deallocate T first and which will suffer a double-free for its troubles. Worse, suppose the taking of the strong reference counter in Rc<T> were spread across three pseudo-instructions:

LOAD counter            (tmp)
ADD counter 1 INTO tmp  (tmp = tmp+1)
STORE tmp INTO counter  (counter = tmp)

Likewise, suppose the dropping of a strong reference counter were spread across three pseudo-instructions:

LOAD counter            (tmp)
SUB counter 1 INTO tmp  (tmp = tmp-1)
STORE tmp INTO counter  (counter = tmp)

In a single-threaded context, this works well, but consider this result in a multi-threaded context. Let counter  be equal to 10 for all threads at the beginning of the following thought experiment:

[A]LOAD counter                        (tmp_a == 10)
[B]LOAD counter                        (tmp_b == 10)
[B]SUB counter 1 INTO tmp              (tmp_b = tmp_b-1)
[A]ADD counter 1 INTO tmp              (tmp_a = tmp_a + 1)
[A]STORE tmp INTO counter              (counter = tmp_a)    == 11
[A]ADD counter 1 INTO tmp              (tmp_a = tmp_a + 1)
[A]STORE tmp INTO counter              (counter = tmp_a)    == 12
[A]ADD counter 1 INTO tmp              (tmp_a = tmp_a + 1)
[A]STORE tmp INTO counter              (counter = tmp_a)    == 13
[B]STORE tmp INTO counter              (counter == tmp_b)   == 9

By the end, we've lost three references to Rc<T>, meaning while T is not lost in memory, it is entirely possible that when we drop T, references will remain to its no longer valid memory out in the wild, the results of which are undefined but not likely to be great.

The Sync trait is derived from Send and has to do with references: T: Sync if &T: Send. That is, a T is Sync only if sharing a &T which acts as if that T were sent into the thread. We know from the previous code that Rc<T>: !Send and so we also know that Rc<T>: !Sync. Rust types inherit their constituent parts' Sync and Send status. By convention, any type which is Sync + Send is called thread-safe. Any type we implement on top of Rc<T> will not be thread-safe. Now, for the most part, Sync and Send are automatically derived traits. UnsafeCell , discussed in Chapter 3, The Rust Memory Model – Ownership, References and Manipulation,  is not thread-safe. Neither are raw pointers, to go with their lack of other safety guarantees. As you poke around Rust code bases, you'll find traits that would otherwise have been derived thread-safe but are marked as not. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.255.140