Guest User

Untitled

a guest
Nov 20th, 2017
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.54 KB | None | 0 0
  1. ### Principles of Programming Languages
  2. ### Assignment 1: Concurrency in Rust
  3. #### Preliminaries
  4. Rust is a low-level language, designed with the goals of zero-overhead memory safety, fearless concurrency, and blazing-fast performance.
  5. Other languages implement concurrency in an opinionated matter, forcing all the code to follow very specific patterns that allow the language to provide good concurrency support, such as clojure's `atom` structure, or Python's Global Interpreter Lock, or Lua's coroutines.
  6. Rust is a low-level language, however, so the overhead of that level of abstraction is (usually) unacceptable.
  7. Rust supports basic threading, message passing, and shared memory out-of-the-box.
  8. Although not originally intended for the purpose, Rust's memory safety mechanisms also make threading much easier and safer.
  9. Combined with Rust's strong typing mechanism, most threading errors get caught at compile-time, and thus the bold claim "fearless concurrency".
  10.  
  11. #### Language builtins
  12. The Rust language itself provides almost no support for threading.
  13. The main support it provides are two marker `traits`:
  14. * `send`: This trait declares that the type can be safely send across threads.
  15. * `sync`: This trait declares that the type can safely be accessed from multiple threads.
  16. All the actual threading and sync support is provided by the standard library.
  17.  
  18. #### Threading
  19. Threads are usually created through the `std::thread::spawn` function, which takes a `closure` and runs it in a new `thread`.
  20. ```rust
  21. use std::thread;
  22.  
  23. fn main() {
  24. thread::spawn(|| {
  25. for i in 1..3 {
  26. println!("{}", i);
  27. }
  28. });
  29. for i in 1..3 {
  30. println!("hi number {} from the main thread!", i);
  31. }
  32. }
  33. ```
  34. This produces the familiar race condition, with two threads both trying to print something as fast as possible.
  35. The `thread::spawn` function returns a `join` handle, which can be used to perform thread joins:
  36. ```rust
  37. let handle = thread::spawn(|| {
  38. for i in 1..10 {
  39. println!("hi");
  40. }
  41. });
  42. handle.join();
  43. ```
  44. The closure is not allowed to access any elements in the surrounding function, since it is possible that it would outlive the scope of the surrounding function(dangling pointers).
  45. To solve this, the clojure can be declared as a `move` closure, which automatically takes ownership of any variables it needs.
  46. ```rust
  47. fn main() {
  48. let v = 1;
  49. let handle = thread::spawn(move || {
  50. println!("{:?}", v);
  51. });
  52. handle.join();
  53. }
  54. ```
  55. #### Message Passing
  56. Message passing channels are the general preferred way to synchronize threads in Rust.
  57. This is implemented as the `std::sync::mspc` function:
  58. (`mspc` stands for multiple-producer, single-consumer)
  59. ```rust
  60. let (tx, rx) = mpsc::channel();
  61. ```
  62. `rx` and `tx` are handles to the receiver and transmitter end of this channel, respectively.
  63. Usage is something like this:
  64. ```rust
  65. fn main() {
  66. let (tx, rx) = mpsc::channel();
  67. thread::spawn(move || {
  68. let val = String::from("hi");
  69. tx.send(val).unwrap();
  70. });
  71. let received = rx.recv().unwrap();
  72. println!("Got: {}", received);
  73. }
  74. ```
  75. An interesting thing to note here is that message passing does not pass the value of the variable.
  76. It passes ownership of the variable to the other side.
  77. This means that this channel performs shared memory through message passing, a clever solution that enables the easily understandable channel semantics while avoiding all the overhead from copying values.
  78.  
  79. #### Shared memory
  80. Shared memory is generally frowned upon by the Rust language as it violates Rust's core principle of single ownership.
  81. However, it is possible to implement safely though the `Mutex` smart pointer.
  82. `Mutex` is defined in `std::sync::Mutex`
  83. It is a generic type, and it is used to wrap a variable, granting access to only one thread at a time.
  84. ```rust
  85. let m = Mutex::new(5);
  86. let mut num = m.lock().unwrap();
  87. *num = 6;
  88. ```
  89. A thread can request access to the contents through the `lock` function, which will block until it acquires the lock, and then return the variable(and gain ownership of it).
  90. In addition, the mutex will automatically unlock when it goes out of scope, solving a class of issues in other languages caused by a thread that forgets to release their lock.
  91. Unfortunately, mutexes still suffer from deadlock issues.
  92. In addition, due to Rust's single-owner semantics a single mutex variable cannot be owned by multiple threads, so in practice a mutex is almost always wrapped in a `std::sync::Arc<T>` type, which is a type of reference counter variable that updates atomically, ensuring thread-safety.
Add Comment
Please, Sign In to add comment