RemoteState company logoHome

RustLLC

Approaches to concurrency in Rust

Posted On : Jun 26, 2023Author : Divyank Aggarwal
RustLLC

Concurrency is a powerful feature that allows multiple tasks to be executed concurrently. All modern programming languages must have powerful tooling to support a robust concurrency model. As a systems programming language, Rust allows us to create and manage system threads and has powerful abstractions like futures to support asynchronous programming but intentionally remains agnostic about the choice of runtime. Consequently, a number of runtimes exist in the Rust ecosystem with their own strengths and weaknesses. In this blog post,we will contrast three popular approaches: using threads directly, Tokio, and Rayon. We will also provide code examples to help illustrate each approach. ​

Using threads

​ Rust's threading model is based on the operating system's threads. It provides an easy-to-use abstraction for creating, managing, and synchronizing threads. Rust's standard library provides the std::thread module, which contains functions for creating and managing threads ​ The following code creates two threads that increment a shared counter variable: ​

use std::thread; use std::sync::{Arc, Mutex}; ​ fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; ​ for _ in 0..2 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { for _ in 0..100000 { let mut num = counter.lock().unwrap(); *num += 1; } }); handles.push(handle); } ​ for handle in handles { handle.join().unwrap(); } ​ println!("Result: {}", *counter.lock().unwrap()); }

​ In this example, we use the Arc (Atomic Reference Counting) and Mutex types from Rust's standard library to create a shared counter variable that can be accessed by multiple threads. The Arc type allows multiple threads to have a shared ownership of the counter variable, while the Mutex type provides a way to synchronize access to the variable. ​

Tokio

​ Tokio is a runtime for writing asynchronous code in Rust. It provides an event-driven, non-blocking I/O model that allows for high-performance network applications. Using tokio, we can spawn tasks that are similar to Go's GoRoutines or Kotlin's Coroutines that are then managed by tokio's runtime to achieve concurrency. The following code shows how to create a simple task to be managed by tokio's runtime: ​

use tokio::task; ​ async fn hello() { println!("Hello from Tokio!"); } ​ #[tokio::main] async fn main() { let handle = tokio::spawn(hello()); ​ handle.await.unwrap(); }

​ In this example, we define an async function hello that prints a message. We then use tokio::spawn to spawn a new task that runs the hello function. We store the JoinHandle returned by tokio::spawn in a variable called handle. ​ We then use the await keyword to wait for the task to complete, and use the unwrap method to retrieve the result. The #[tokio::main] attribute is used to indicate that the main function should run using the Tokio runtime. ​

Rayon

​ Rayon is a library for data parallelism in Rust. It provides an easy-to-use API for parallelizing computations over collections. Contrasted by tokio, Rayon's utility lies in parallelizing computationally heavy tasks rather than IO-bound tasks. It will automatically decide how to divide your data into tasks and dynamically adapt for high performance. As a tradeoff, significant performance improvements will only manifest on large datasets and heavy computation. On a small dataset, using Rayon might be slower than sequential processing because of the overhead of parallelization. ​ The following code shows how to use Rayon to parallelize the computation of the sum of a vector: ​

use rayon::prelude::*; ​ fn main() { let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let sum: i32 = v.par_iter().sum(); println!("Sum: {}", sum); }

​ In this example, we use the par_iter() method from Rayon's standard library to create a parallel iterator over the vector. The sum() method is called on the parallel iterator to compute the sum of the vector. Rayon automatically parallelizes the computation over multiple threads, making it easy to write efficient and scalable code. ​

Conclusion

​ In conclusion, Rust provides different concurrency models, each with its own strengths and weaknesses. Using threads is the most basic way to introduce concurrency in Rust, while Tokio provides an event-driven, non-blocking I/O model for high-performance network applications. Rayon is a library for data parallelism in Rust, providing an easy-to-use API for parallelizing computations over collections. Understanding the strengths and weaknesses of each concurrency model is important for writing efficient and scalable Rust code.

Concurrency is a powerful feature that allows multiple tasks to be executed concurrently. All modern programming languages must have powerful tooling to support a robust concurrency model.

Ready to Collaborate?

We’ll respond within one business day. Connect to plan a solution that advances your product and business.

Email Us Logo

Email Us

gtm@remotestate.com

Call Us Logo

Call Us

USA: +1 - 210 972 5958

India: +91 - 977 676 7574

Our Offices Logo

Our Offices

USA - 2219 Main Street, Santa Monica, CA 90405

India - Block C, ATS BOUQUET, C 401, Block B, Sector 132, Noida, Uttar Pradesh 201304

Get a Consultation

Approaches to concurrency in Rust | RemoteState