Wednesday, June 9, 2021

Fearless Concurrency in Rust (Guide)





Handling concurrent programming safely and efficiently is another of Rust's major goals. Concurrent programming, where different parts of a program execute independently, and parallel programming, where different parts of a program execute at the same time, are becoming increasingly important as more computers take advantage of their multiple processors. 


What is concurrency
Concurrency is when sections of code runs parallel to other sections of code.
Typically, a program would run its code sequentially, section after section. By using threads, we can run our section of code at the same time as other sections.

Let's take a look an example



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

fn main() {
    for i in 0..5 {
        println!("Perulangan 1 : {}", i);
        // wait a bit before next iteration
        // for demonstration purposes
        thread::sleep(Duration::from_millis(500));
    }

    for i in 0..5 {
        println!("Perulangan 2 : {}", i);
        thread::sleep(Duration::from_millis(500));
    }
}



Output:

Perulangan 1 : 0
Perulangan 1 : 1
Perulangan 1 : 2
Perulangan 1 : 3
Perulangan 1 : 4
Perulangan 2 : 0
Perulangan 2 : 1
Perulangan 2 : 2
Perulangan 2 : 3
Perulangan 2 : 4



How to create a thread
To create thread, we need to use necessary modules, std::thread and std::time::Duration modules. Then we use the thread::spawn function.  

Syntax:
thread::spawn(|| { /* code to execute in the thread */ })

Example

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

fn main() {
    //create thread
    thread::spawn(|| {
        // everything in here runs
        // in its own separate thread
        for i in 0..5 {
            println!("Perulangan 2 : {}", i);
            thread::sleep(Duration::from_millis(500));
        }
    });

    for i in 0..5 {
        println!("Perulangan 1 : {}", i);
        thread::sleep(Duration::from_millis(500));
    }
}


Output:

Perulangan 1 : 0
Perulangan 2 : 0
Perulangan 1 : 1
Perulangan 2 : 1
Perulangan 1 : 2
Perulangan 2 : 2
Perulangan 1 : 3
Perulangan 2 : 3
Perulangan 1 : 4
Perulangan 2 : 4


How to join handles
To join the handles, we can use .join() method on the handle name and we use .unwrap() to handle possible errors.

Example:

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

fn main() {
    //create thread
    let handle = thread::spawn(|| {
        // everything in here runs
        // in its own separate thread
        for i in 0..10 {
            println!("Perulangan 2 : {}", i);
            thread::sleep(Duration::from_millis(500));
        }
    });

    for i in 0..5 {
        println!("Perulangan 1 : {}", i);
        thread::sleep(Duration::from_millis(500));
    }

    handle.join().unwrap();
}



Output:
Perulangan 1 : 0
Perulangan 2 : 0
Perulangan 1 : 1
Perulangan 2 : 1
Perulangan 1 : 2
Perulangan 2 : 2
Perulangan 1 : 3
Perulangan 2 : 3
Perulangan 1 : 4
Perulangan 2 : 4
Perulangan 2 : 5
Perulangan 2 : 6
Perulangan 2 : 7
Perulangan 2 : 8
Perulangan 2 : 9

What will happen if we are not using  .join() method in the variable handle. Let's take a look an example below:


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

fn main() {
    //create thread
    let handle = thread::spawn(|| {
        // everything in here runs
        // in its own separate thread
        for i in 0..10 {
            println!("Perulangan 2 : {}", i);
            thread::sleep(Duration::from_millis(500));
        }
    });

    for i in 0..5 {
        println!("Perulangan 1 : {}", i);
        thread::sleep(Duration::from_millis(500));
    }

    // handle.join().unwrap();
}


Output:
Perulangan 1 : 0
Perulangan 2 : 0
Perulangan 2 : 1
Perulangan 1 : 1
Perulangan 2 : 2
Perulangan 1 : 2
Perulangan 2 : 3
Perulangan 1 : 3
Perulangan 2 : 4
Perulangan 1 : 4
Perulangan 2 : 5

The loop in "Perulangan 2" is printed until step 5 due to the main thread in "Perulangan 1" has finished. To prevent this case happened, we need to use  .join() method.


How to take ownership from inside a thread
To take ownership, we use move keyword.    

Example:

use std::thread;

fn main() {
    let nomor: i32 = 5;

    let handle = thread::spawn(move || {
        // try to access the
        // variable outside
        // of this thread
        println!("nomor: {}", nomor);
    });

    handle.join().unwrap();
}


Output:
nomor: 5



How to send messages between threads
We need to use std::sync::mpsc module (mpsc :Multiple Producer Single Consumer). Next, we need to create the transmitter and the receiver, which will be used to transfer information through the channel.   

Example:

use std::sync::mpsc;
use std::thread;

fn main() {
    // create send/receiver vars
    // to move data through channel
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        // value to be sent
        let transfer_num: i32 = 5;

        // the send() function will send
        // a value to the receiver (rx)
        tx.send(transfer_num).unwrap();
    });

    // catch the value with the recv()
    // function and store it in 'b'
    let receive_num: i32 = rx.recv().unwrap();

    println!("Value receive : {}", receive_num);
}


Output:
Value receive : 5

When you run the program, it was successfully sent and received.