Changes

Jump to: navigation, search

Team Hortons

8,920 bytes added, 14:10, 15 December 2017
Sources
= Multithreading Parallelism with Rust =
== Introduction: The Rust Programming language ==
let mut vec = Vec::new();
let (sendsender, receivereceiver): (Sender<i32>, Receiver<i32>) =
mpsc::channel();
for i in 0..NUM_THREADS {
// cloning the sender so it can be exclusive to // the new thread let thread_sender = sendsender.clone();
thread::spawn(move || {
for _ in 0..NUM_THREADS {
vec.push(receivereceiver.recv().unwrap());
}
== Locks (Mutex) ==
Rust language also supports shared-state concurrency which allows two or more processes have some shared state (data) between them they can write to and read from. Sharing data between multiple threads can get pretty complicated since it introduces the strong hazard of race conditions. Imagine a situation when one thread grabs some data and attempts to change it, while another thread is starting to read the same value, there’s no way to predict if latter one retrieves updated data or if it gets hold of the old value. Thus shared-state concurrency has to deliver some implementation of guard and synchronized access mechanism.[text[File:RustMutex.png|thumb|left|alt=Mutex|Acquire/Release Lock Process ]The access to shared state (critical section) and synchronization methods are typically implemented through ‘mutex’. Mutual exclusion principles (mutex) provide ways to prevent race conditions by only allowing one thread to reach out some data at any given time. If one thread wants to read/write some piece of data, it must first give a signal and then, once permitted, acquire the mutex’s lock. The lock is a special data structure that can keep track of who currently has exclusive access to the data. You can think of locks as dedicated mechanism that guards critical section. Since Rust Lang supports ownership principles, the threads are inaccessible to each other automatically and only one thread can access data at any given time time.  The snippet below demonstrates how you can create and access mutex in rustlang:  <source lang="rust">fn use_lock(mutex: &Mutex<Vec<i32>>) { let mut guard = lock(mutex); // take ownership let numbers = access(&mut guard); // borrow access numbers.push(42); // change the data} </source> Mutex construct is generic and it accepts the piece of shared data protected by the lock. It is important to remember that the ownership of that data is transferred into the mutex structure at its creation. The lock function will attempt to acquire the lock by blocking the local thread until it is available to obtain the mutex. The data is automatically unlocked once the return value of lock() function gets released (at the end of the function scope) so there is no need to release the mutex lock manually.
== RC and Atomic ==
In Rust, some data types are defined as “thread safe” while others are not. For example, Rc<T> type, which is Rust’s own implementation of “smart pointer”, is considered to be unsafe to share across threads. This type keeps track of number of references and increments/decrements count each time a new reference is created or old one gets destroyed. Rc<T> does not enforce any mechanisms that makes sure that changes to the reference counter value can’t be interrupted by another thread. The snippet below demonstrates the issue an results in compiler error: <source lang="rust">let mutex = Rc::new(Mutex::new(2));let mut handles = vec![text]; for _ in 0..4 { let mutex = mutex.clone(); let handle = thread::spawn(move || { let mut num = mutex.lock().unwrap();  *num *= 2; println!("Intermediate Result : {}", *num); }); handles.push(handle);} for handle in handles { handle.join().unwrap();} println!("Final Result: {}", *mutex.lock().unwrap());</source> This will produce an error : <source lang="rust">error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>: std::marker::Send` is not satisfied in `[closure@src/main.rs:11:32: 16:6 mutex:std::rc::Rc<std::sync::Mutex<i32>>]` --> src/main.rs:11:18 |11 | let handle = thread::spawn(move || { | ^^^^^^^^^^^^^ `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely | = help: within `[closure@src/main.rs:11:32: 16:6 mutex:std::rc::Rc<std::sync::Mutex<i32>>]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::sync::Mutex<i32>>` = note: required because it appears within the type `[closure@src/main.rs:11:32: 16:6 mutex:std::rc::Rc<std::sync::Mutex<i32>>]` = note: required by `std::thread::spawn` </source> Luckily for us, Rust ships another thread-safe implementation of ‘smart pointer’ called Arc<T> which implements reference counting via atomic operations. It is important to note that in serial code, it makes much more sense to use standard Rc<T> over Arc<T> type since the latter structure is more expensive performance-wise. <source lang="rust">use std::sync::{Arc, Mutex};use std::thread; fn main() {let mutex = Arc::new(Mutex::new(2));let mut handles = vec![]; for _ in 0..4 { let mutex = mutex.clone(); let handle = thread::spawn(move || { let mut num = mutex.lock().unwrap();  *num *= 2; println!("Intermediate Result : {}", *num); }); handles.push(handle);} for handle in handles { handle.join().unwrap(); } println!("Final Result: {}", *mutex.lock().unwrap()); } </source> This will produce the expected result : <source>Intermediate Result : 4Intermediate Result : 8Intermediate Result : 16Intermediate Result : 32Final Result: 32</source> This time the code worked. This example was very simple and not too impressive. Much more complicated algorithms can be implemented with the Mutex<T>. == Rayon Library ===== Introduction ===  Rust lang users and community are heavily involved in development of language extensions. Although core features offer quite a bit of programming tools on their own, there is still a demand for more complex functionality with a higher-level API. Rust has its own package registry that stores various libraries that can satisfy programmers needs. These packages vary from networking tools, web services to image processing and multi-threading libraries.One package can be of particular interest to parallel programming enthusiasts – it is a data-parallelism library called Rayon. The package is praised to be extremely lightweight and it makes it easy to convert a sequential computation into a parallel one. It also abstracts some Rust-lang threading boilerplate code and makes coding parallel tasks a bit more fun.Just like TBB, it can be used with iterators, where iterator chains are executed in parallel. It also supports join model and provides ways to convert recursive, divide-and-conquer style problems into parallel implementations.  === Parallel Iterators === Rayon offers experimental API called “parallel iterators”. Example below demonstrates parallel sum of squares function: <source lang="rust">use rayon::prelude::*;fn sum_of_squares(input: &[i32]) -> i32 { input.par_iter() .map(|&i| i * i) .sum()}</source> === Using Join for Divide-and-Conquer Problems ===Parallel iterators are implemented with a more primitive method called join which takes 2 closures and runs them in parallel. The code snippet below demonstrates increment algorithm: <source lang="rust"> fn increment_all(slice: &mut [i32]) { if slice.len() < 1000 { for p in slice { *p += 1; } } else { let mid_point = slice.len() / 2; let (left, right) = slice.split_at_mut(mid_point); rayon::join(|| increment_all(left), || increment_all(right)); }}</source> === How it works ===Rayon borrowed work stealing concepts from Cilk project. The library attempts to dynamically ascertain how much multi-threading resources are available. The idea is very simple: there is always a pool of worker threads available, waiting for some work to do. When you call join the first time, we shift over into that pool of threads.  [[File:Rust_Join1.png]] But if you call join(a, b) from a worker thread W, then W will place b into its work queue, advertising that this is work that other worker threads might help out with. W will then start executing a. While W is busy with a, other threads might come along and take b from its queue. That is called stealing b.  [[File:Rust_Join_2.png|600px]] Once a is done, W checks whether b was stolen by another thread and, if not, executes b itself. If W runs out of jobs in its own queue, it will look through the other threads' queues and try to steal work from them.
[[File:Rust_Join_3.png|600px]]
== Group Members ==
# [mailto:obelavina@myseneca.ca Olga Belavina]
== Sources ==
* [https://doc.rust-lang.org/book/second-edition/ch03-01-variables-and-mutability.html Variables and Mutability]
* [https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html Ownership]
* [https://doc.rust-lang.org/book/second-edition/ch04-02-references-and-borrowing.html References and Borrowing]
* [https://doc.rust-lang.org/std/sync/atomic/struct.AtomicPtr.html Atomic Pointers]
* [https://doc.rust-lang.org/std/sync/struct.Mutex.html Mutex]
* [https://docs.rs/rayon/0.9.0/rayon/ Rayon Library]
* [https://static.rust-lang.org/doc/master/std/sync/mpsc/index.html Channels]
* [https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html Fearless Concurrency with Rust]
== Progress ==
-5%

Navigation menu