reviewpad / hey-robin

This repository is an experiment for people to access LLMs and communicate in a group using Robin, Reviewpad's AI agent 🔮
8 stars 0 forks source link

Rust concurrency #8

Open rkrzr opened 1 year ago

rkrzr commented 1 year ago

Hey @robinaidev, what are the top 3 ways to write a concurrent program in Rust? Please elaborate on the pros and cons of each approach and compare them to each other.

robinaidev commented 1 year ago

Hello @rkrzr! Rust provides several ways to write concurrent programs. Three commonly used ways are:

  1. Using threads and message passing with channels.
  2. Using shared memory concurrency with mutexes.
  3. Using async/await with Tokio or async-std libraries.

Each approach has its own advantages and disadvantages. Using threads with message passing provides good control over resources and is easier to reason about. However, it can be harder to debug and performance may be slower than alternatives.

Using shared memory concurrency with mutexes is faster than using threads with message passing. It works well for programs that need to share data between threads, but can also lead to race conditions and deadlocks that are harder to debug.

Using async/await with Tokio or async-std libraries can be the most efficient way to write concurrent programs, especially those that need to handle a lot of I/O. However, it can be harder to understand and write due to its complexity.

Overall, the choice of approach depends on the specific requirements of the program.

marcelosousa commented 1 year ago

The key thing here is that we can now have a conversation @rkrzr . @robinaidev can decide when it should reply.

marcelosousa commented 1 year ago

@robinaidev can you share some code examples on each of the three commonly used ways?

robinaidev commented 1 year ago

Sure, here are some code examples for each approach:

  1. Using threads and message passing with channels:
use std::thread;
use std::sync::mpsc;

fn main() {
    let (sender, receiver) = mpsc::channel();

    for i in 0..10 {
        let child_sender = sender.clone();
        thread::spawn(move || {
            child_sender.send(i).unwrap();
        });
    }

    for _ in 0..10 {
        println!("{}", receiver.recv().unwrap());
    }
}
  1. Using shared memory concurrency with mutexes:
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("{}", *counter.lock().unwrap());
}
  1. Using async/await with Tokio or async-std libraries:
use std::error::Error;
use tokio::io::{self, AsyncReadExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;

    loop {
        let (mut socket, _) = listener.accept().await?;

        tokio::spawn(async move {
            let mut buf = [0; 1024];

            loop {
                let n = match socket.read(&mut buf).await {
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("failed to read from socket; err = {:?}", e);
                        return;
                    }
                };

                if let Err(e) = socket.write_all(&buf[0..n]).await {
                    eprintln!("failed to write to socket; err = {:?}", e);
                    return;
                }
            }
        });
    }
}

I hope these code examples help you understand the three approaches better!