Rust lang series episode #29 — threads (#rust-series)

in #rust-series8 years ago (edited)

Hello everyone, new Rust lang series episode is here. Topic of this episode is working with additional threads.

As even mid-range mobile phones CPU today contain multiple cores and each of them is individual processing units it's good to know how to utilize them.

So far all our code was running on single thread and everything was executed in main thread. If we want to utilize more of multi-core CPU potential, we have to think about concurrency and splitting processing between additional threads.

Concurrency traits

Its worth to say that most Rust types are not thread-safe by default. That means they cannot assure reliable behavior in multi-thread environment. There are special traits in Rust which provides key guarantees.

  • Send indicates ability to safely transfer ownership between threads.
  • Sync indicates that type has no interior mutability.

Running multiple threads

We will create three additional threads which will print greeting.

use std::thread;
use std::time::Duration;

fn main() {
    for i in 1..4 {
        thread::spawn(move || {
            println!("Hello Steemit from thread {}", i);
        });
    }
    thread::sleep(Duration::from_millis(100));
    println!("All work done!")
}

Output

Hello Steemit from thread 1
Hello Steemit from thread 2
Hello Steemit from thread 3
All work done!

Breaking code

std::thread provides API for creating additional threads

spawn() is method for creating thread with given closure. Thread is creating and running method returns thread JoinHandle.

sleep() thread method puts current thread to sleep for Duration time. In this case it makes main thread sleeping to give some time to other threads to finish.

Memory shared by multiple threads

This is more complicated because we need to assure atomicity, that means that each data update by each thread is done correctly and there is no possibility to get invalid data or out-of-sync writing.

In example below we will take vector which will be correctly updated (vector data will be incremented) by five individual threads.

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
    let data_arc = Arc::new(Mutex::new(vec![0,0,0]));

    for i in 0..5 {
        let data_arc_for_thread = data_arc.clone();
        thread::spawn(move || {
            let mut data_guard = data_arc_for_thread.lock().unwrap();
            for j in 0..3 {
                data_guard[j] += i;
            }
            println!("thread {} work done", i);
        });
    }

    thread::sleep(Duration::from_millis(100));

    println!("Final result from parallel computation: {:?}", data_arc);
}

Output

Output can be something like this (processing order can differ):

thread 1 work done
thread 3 work done
thread 0 work done
thread 4 work done
thread 2 work done
Final result from parallel computation: Mutex { data: [10, 10, 10] }

Breaking down

Arc

Arc is atomic-Rc, marked as Send.

  • guaranties: thread-safe, destructed when last Arc is out of scope
  • cost: atomic ref-count update, ref-count update cost

Mutex

Provides mutual-exclusion via RAII guards (object-like guards)

  • guaranties: shared mutability across threads
  • cost: costly atomic-like types for locks, waiting for locks

lock() method acquires a mutex guard, blocking the current thread until it's able to get it. Mutex is unlocked as guard goes out of scope. Returns LockResult

type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;

Postfix

That's all for now, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.

Meanwhile you can also check the official documentation for additional related information:

#rust-series
#rust-lang
#rust

Coin Marketplace

STEEM 0.19
TRX 0.13
JST 0.028
BTC 66081.13
ETH 3315.51
USDT 1.00
SBD 2.69